Keywords in context
KWIC stands for “keywords in context”. We can use KWIC to find
stories that contain a term, and get the text surrounding that word.
This code would pull all documents containing terms that started with
“immig” (so: immigrant, immigration, immigrants etc.) along with a
window of 3 words on either side.
(note that this is actually pulling from the pol_tokens
object rather than the DFM, this approach doesn’t really require any
cleaning to use)
# keywords in context
kw_immig <- kwic(pol_tokens, pattern = "immig*", window=3)
# view the first 10
head(kw_immig, 5)
For your own work, you may want to get more context by increasing the
window argument in the KWIC command. You might also
consider writing these results to a csv file so you can read them in a
spreadsheet. KWIC doesn’t automatically provide you with nice looking
plots, but it can be a very good way to do things like develop a
dictionary or get a general qualitative sense of how sources are
discussing something.
Dictionary Methods
We can use the dictionary approach to simply match terms against a
dictionary of terms we’re interested in. The policy agendas dictionary
contains a list of terms associated with major policy areas. Here, we
apply the agendas codebook to the tokens list, which removes all terms
that aren’t part of the dictionary.
# download the policy agendas codebook (discussed here #https://www.almendron.com/tribuna/wp-content/uploads/2017/05/CAP2013v2.pdf)
policyAgendas<-readRDS(url('https://github.com/Neilblund/APAN/raw/main/policy_agendas_dictionary.rds'))
immig_tokens<-tokens_subset(pol_tokens,
# get a subset of articles that mention Hunter Biden
subset = str_detect(pol_corpus, "immigr*"))
# convert to a quanteda dictionary format
policyagendas.dict <- dictionary(policyAgendas)
# apply the dictionary
agenda_politics <- tokens_lookup(pol_tokens, dictionary = policyagendas.dict, levels = 1)
# check out the first result:
agenda_politics[[1]]
## [1] "healthcare" "healthcare" "healthcare"
## [4] "finance" "healthcare" "healthcare"
## [7] "healthcare" "healthcare" "healthcare"
## [10] "healthcare" "intl_affairs" "healthcare"
## [13] "healthcare" "crime" "healthcare"
## [16] "healthcare" "healthcare" "healthcare"
## [19] "healthcare" "healthcare" "healthcare"
## [22] "healthcare" "healthcare" "healthcare"
## [25] "healthcare" "land-water-management"
We can convert this list of tokens to a document feature matrix. A
DFM will have one row per document and one column per feature, the cells
will contain counts of the number of times a given feature occurs in
each document. In this case, our DFM will have one column for each of
the topics in the policy agendas dictionary. We can use this to do
things like count the number of documents which are about some
topic.
# convert to a dfm
agenda_dfm<-dfm(agenda_politics)
# look at the first few rows
head(agenda_dfm, n=5)
## Document-feature matrix of: 5 documents, 28 features (82.86% sparse) and 4 docvars.
## features
## docs macroeconomics civil_rights healthcare agriculture forestry labour
## text1 0 0 22 0 0 0
## text2 0 0 10 0 0 0
## text3 0 0 3 0 0 1
## text4 1 0 1 0 0 1
## text5 0 0 1 0 0 0
## features
## docs immigration education environment energy
## text1 0 0 0 0
## text2 0 0 0 0
## text3 0 0 0 0
## text4 0 0 0 0
## text5 0 0 0 0
## [ reached max_nfeat ... 18 more features ]
textstat_frequency gives us some basic stats about our
document features. The “frequency” column is the number of times a
policy agenda topic occurs. The “docfreq” column is the number of
documents which contain that agenda topic (regardless of whether it
occurs multiple times in a single document). From this, we can see that
“healthcare” is the most commonly occurring agenda topic.
agenda_freq<-textstat_frequency(agenda_dfm)
head(agenda_freq)
Put them all in a horizontal barplot:
agenda_freq%>%
ggplot(aes(x = reorder(feature, docfreq), y=docfreq)) +
geom_bar(stat='identity', fill='lightblue') +
xlab('policy agenda') +
ylab('number of documents mentioning') +
coord_flip() +
# using the minimal theme:
theme_minimal() +
ggtitle('Document frequency of policy agenda terms in CNN politics articles')

Additional dictionaries/commands
Quanteda does have some additional dictionaries that may be of use
for some of you. These can be accessed through the Quanteda.dictionaries
package and the quanteda.sentiment
package. Although these don’t appear to be on CRAN yet, you can install
them by running the command below, and then loading them the way you
would load any other library. The associated github page has information
on the included dictionary files.
devtools::install_github("kbenoit/quanteda.dictionaries")
remotes::install_github("quanteda/quanteda.sentiment")
The textstat_polarity
function will automatically apply a dictionary and measure sentence
polarity by getting the log of positive/negative terms in the text. The
textstat_valence
function can be used to compute scores for dictionaries that have
multiple categories rather than just positive vs. negative.
Here’s an example of how you might use the
textstat_polarity command from this package along with the
Lexicoder Sentiment
Dictionary. In this example, we’re measuring the document polarity,
then graphing the average sentiment for documents for each source and
depending on whether they mention immigration. The results suggest that
Fox News tends to give more negative coverage when discussing
immigration compared to their coverage of other topics, where as CNN
covers immigration with a slightly more positive valence.
library(quanteda.sentiment)
# load the lexicoder sentiment dictionary
lsd<-quanteda.sentiment::data_dictionary_LSD2015
polarity<-textstat_polarity(pol_corpus, dictionary=lsd)%>%
# add polarity to existing document variables
bind_cols(docvars(pol_corpus))%>%
# also add a column for mentions of immigration
mutate(mentions_immigration = str_detect(pol_corpus,"immigr*"))
# plot polarity for all four groups
ggplot(polarity, aes(x=source, y=sentiment, fill=mentions_immigration)) +
geom_boxplot(notch=TRUE) +
theme_minimal()

# test as an interaction term:
## summary(lm(sentiment ~ source * mentions_immigration, data=polarity))
Making a custom dictionary
Finally, keep in mind that you can create a dictionary from a named
list. So if you want to create your own for a specific domain or
language, or extend an existing one, the syntax should be reasonably
straightforward.
custom_list<-list(COVID =c('covid','coronavirus', 'vaccin*', 'lock down'),
Jan6 = c( "jan* 6*", "capitol", 'insurrection*'))
custom_dict <- dictionary(custom_list)
tok<-tokens_lookup(pol_tokens, dictionary=custom_dict)
table(unlist(tok))
Any of these approaches are a good starting point and I think you can
definitely use them for your analyses, but you should be willing to
admit their limitations when applied outside the area they were designed
for. Check out this
paper for some discussion of the limitations of dictionary based
approaches.
Topic Modeling
LDA
Latent Dirichlet Allocation (LDA) also known as topic modeling is
widely used approach for unsupervised document classification.
“Unsupervised” in this case, means that you don’t need any prior
knowledge about the corpus in order to make use of it. Instead, an LDA
model will attempt to model documents as as a probability distribution
over topics, and topics as a probability distribution over words. This
is done automatically - all you really need to do is clean the text and
choose a number of topics. That said, this also means that the results
can be somewhat hard to interpret: you’ll have to make an educated guess
about what topics you’re finding by looking at words that occur with a
higher frequency in a given topic.
Note: If you’re looking for a quick mostly non-technical primer on
this, I think Edwin Chen’s blog
post is a good starting point, but there’s a lot of information
about these online. If you’re looking for a deeper dive, here’s
a good review article on the method and recent innovations.
Here’s an example of creating an LDA model with just 15 topics.
(If you want replicable result, make sure to run
set.seed([some number]) right before you train the topic
model. While topic models will theoretically converge toward a single
“correct” answer, the numbering of the topics is completely arbitrary in
standard LDA, so topic 10 might be topic 2 next time you run it.)
library(seededlda) # package for both seeded LDA and regular LDA
library(LDAvis) # for visualization
# number of topics:
K<-15
# the initialization is random, so set the random number seed for replicability
set.seed(100)
lda_model<-textmodel_lda(pol_dfm, k = K,
max_iter=2000,
auto_iter = T,
# raising this result in more uniform theta
alpha = 0.1,
#raising this will result in a more uniform phi
beta = 0.01
)
If you view lda_model$phi you should see a matrix with
15 rows (one row per topic) and around 8000 columns (one column per
term). The number in each row represent the probability of that term for
the topic. (you’ll sometimes see people refer to this matrix using the
Greek letter \(\phi\) or as the
“topic-word distribution”)
You can see the highest probability words in each topic using the
command below, you’ll probably be able to pick out some concepts pretty
quickly
#top 10 terms per topic
terms(lda_model, 10)%>%
#converting to data frame (for prettier formatting)
data.frame()
Meanwhile, the lda_model$theta represents the topic-word
distribution matrix. If you view this object you should see one column
per topic and one row per document. The numbers indicate the probability
of a topic in a given document. (you’ll often see this distribution
denoted as \(\theta\) or as the
“document-topic matrix”)
# get the document-topic distributions for the first few articles:
head(lda_model$theta)%>%
data.frame()
One use of the the document topic distribution is for information
retrieval. Based on the most likely terms, topic 13 appears to be
picking up on stories related to the Supreme Court’s decision
overturning Roe v. Wade. So documents with a high % of this topic should
probably be about that story. And based on the urls, this intuition
seems correct:
#get document data
dobbs_docs<-docvars(pol_dfm)%>%
# add % of topic 13 to document data
mutate(dobbs = lda_model$theta[,4])%>%
# sort from lowest to highest topic %
arrange(-dobbs)
dobbs_docs%>%
select(url)%>%
slice_head(n=10)
We can use this insight to get some sense of how the volume of
coverage on this topic has changed over time. Here, we’re getting the
monthly average % of this topic within each document. The
dobbs_timeline object just allows us to add a set of
annotations to the timeline.
# make a timeline of events (so we can add annotations to the timeline)
dobbs_timeline<-data.frame(event= c('Ginsburg death',
'Cert granted',
'Oral arguments',
'Dobbs leak',
'Dobbs opinion'),
date = as.Date(c("2020-09-18",
'2021-05-17',
'2021-12-01',
'2022-05-02',
'2022-06-24'))
)%>%
# convert timeline to months (to allow joining with monthly coverage data)
mutate(month = floor_date(date, unit='months'))
monthly<-dobbs_docs%>%
# round down to the nearest month
mutate(month = floor_date(date, unit='months')
)%>%
# group by month and compute the monthly average:
group_by(month)%>%
summarise(coverage = mean(dobbs)
)%>%
# join with timeline data
left_join(dobbs_timeline)
And from here, we’ll create the plot in GGplot:
# create plot:
monthly%>%
ggplot(aes(x=month, y=coverage, label=event)) +
# create the line plot
geom_line() +
# add points for each month
geom_point() +
# turn off clipping (avoids removing labels if they're outside the plot area)
coord_cartesian(clip = "off") +
# add labels (vjust -1 sets annotation just above the timeline)
geom_label(vjust=-1) +
ylab('Average topic %') +
# using the minimal theme:
theme_minimal() +
ggtitle('Average percent topic 13 (Roe overturned)') +
# show ticks every 3 months
scale_x_date(breaks="3 month", date_labels = "%Ob '%y") +
# labels at 45 degree angle
theme(axis.text.x = element_text(angle = 45))

Lastly, you can use the LDAvis package to
visualize and interpret your topics.
The left panel uses a principal
component analysis to model the “distance” between each topic.
Topics that are further apart are less similar to one another, and
larger bubbles indicate the topic is more common. Selecting a topic will
cause the right panel to display relevant terms for that topic. Note:
clicking the links in the bottom right will bring up more detailed
information on what these metrics mean, but the short version is that
they’re both meant to make it easier to identify the most distinctive
terms for each topic, rather than the terms that have the highest
frequency.
In order to (hopefully) simplify the process of setup, there’s a
custom function included in the header of this notebook that will allow
you to save and embed these visualizations. Here’s the code you would
use to make this display on your local device:
# a filename to save the plot under
modelvis<-'lda_vis.html'
# create the visualization
LDAvis_standalone(lda_model, outputfile=modelvis)
# bring up a window with the visualization:
browseURL(modelvis)
You could take screenshots of this visualization for different topics
in order to share it, but if you wanted to share the actual interactive
version of this visualization in an Rmarkdown file or notebook, you
could do it using the includeHTML function from the
htmltools package.
lda_vis<-'lda_visual.html'
LDAvis_standalone(lda_model, outputfile=lda_vis)
htmltools::includeHTML(lda_vis)
Seeded LDA
In seeded LDA, you supply some initial terms and topics, and the
model will infer additional terms based on your words.
Here I’ve created a custom dictionary with 10 topics and just a
handful of terms for each. Notice that terms with * will be
able to match different endings, so vaccin* should be able
to match any of vaccinate, vaccine, and vaccination.
news_dict<-list('covid' = c('covid*', 'vaccin*', 'lockdown*'),
'natsec' = c('afghan*', 'taliban', 'putin', 'china', 'ukrain*','zelensky', 'terror*'),
'elections' = c('election', 'caucus', 'primary', 'debate*', 'nomination', 'midterm', 'vote*', 'poll*'),
'trump' = c('indictment', 'impeachment', 'mar-a-lago'),
'jan6' = c("capitol", 'january', 'chansley','fraud'),
'blm' = c('floyd' , 'black', 'police', "protest*"),
'immigration' = c('immigrat*', 'border', 'homeland', 'migrant*'),
'climate' = c('global','climate*','fuel', 'carbon', 'emission*'),
'abortion' = c('abort*', 'roe', 'wade', 'fetus', 'dobbs'),
'economy' = c('inflation','price*', 'tax')
)
# conver to a quanteda dictionary
dict_topic<-dictionary(news_dict)
set.seed(100) # setting random number seed
slda_model <- textmodel_seededlda(pol_dfm,
dictionary = dict_topic,
auto_iter = T,
residual = 5, # 5 residual topics for garbage
max_iter=2000)
Again, we can extract topics from this model using the
terms function. You’ll notice that there are topics that
correspond to the categories in the dictionary, but there are lots of
words that weren’t initially given to the model. One useful application
of this approach is it can help you come up additional terms when
constructing a dictionary.
terms(slda_model)%>%
#converting to a data frame (just to make it look nicer in the notebook)
data.frame()
And, just like with regular LDA, we can use the inferred topics to do
things like track news coverage over time (note that the columns now
have names based on our dictionary) Here’s how coverage of Covid-19
topic steadily dropped since 2020.
covid_df<-
docvars(pol_dfm)%>%
# add % of covid topic
mutate(covid = slda_model$theta[,'covid'],
# round down to the nearest month
month = floor_date(date, unit='months'))%>%
# group by month and compute the monthly average:
group_by(month)%>%
summarise(covid = mean(covid))
covid_df%>%
ggplot(aes(x=month, y=covid)) +
# create the line plot
geom_line() +
geom_point() +
#add a marker for the start of Floyd protests:
ylab('Average topic %') +
# using the minimal theme:
theme_minimal() +
ggtitle('Average % covid topic by month') +
scale_x_date(breaks="3 month", date_labels = "%Ob '%y") +
# labels at 45 degree angle
theme(axis.text.x = element_text(angle = 45))

Lastly, the same method we used for LDAVis above should work here
(unfortunately this doesn’t pick up on the topic names)
slda_vis<-'slda_vis.html'
LDAvis_standalone(slda_model, outputfile=slda_vis)
htmltools::includeHTML(slda_vis)
Structural Topic Models
Unlike traditional LDA, Structural Topic Models (paper)
allow users to include additional document-level data when inferring
their topics. The “prevalence model” will allow topic probabilities to
vary systematically according to a characteristic of the document (such
as the author or source), the “content model” will allow the
term-probabilities within each topic to vary in the same way. For
instance: if we think that different sources are more likely to talk
about certain issues, we can explicitly model that information as part
of the inference process. Here, we’re using the source variable (Fox
News vs. CNN) as a predictor for the topic probabilities. The assumption
(a reasonably safe one) is that the two outlets will differ
systematically in the sorts of news they cover.
library(stm) # for structural topic models
# convert DFM to a format usable by STM
dfm2stm <- convert(pol_dfm, to = "stm")
# number of topics
K<-15
# random number seed
set.seed(1000)
# run the model
stm_model <- stm(dfm2stm$documents, dfm2stm$vocab,
K = K,
data = dfm2stm$meta,
# include source in the prevalence model
prevalence = ~ source,
init.type = "Spectral",
# set verbose =T if you want to watch the model progress
verbose = F
)
The STM package includes a special labelTopics function
to retrieve the terms associated with each topic. While the highest
probability terms can be a good way to identify topics, it can be
somewhat unhelpful because sometimes terms show up at the topic of the
list because they’re just common words in general. The FREX, lift, and
score metrics all attempt to account for term frequency in some way in
order to give greater weight to terms that are more distinct to each
topic.
labelTopics(stm_model, topics=1:5, n=5) # note - only showing first 5 of 15 topics here
## Topic 1 Top Words:
## Highest Prob: vaccine, testing, mask, virus, mandate
## FREX: vaccine, mask, virus, fauci, newsom
## Lift: niaid, stay-at-home, unvaccinated, allergy, azar
## Score: vaccine, mask, virus, fauci, cdc
## Topic 2 Top Words:
## Highest Prob: impeachment, prosecutors, trial, pence, indictment
## FREX: jury, indictment, giuliani, trial, insurrection
## Lift: jurors, clemency, fani, sidney, cipollone
## Score: indictment, riley, prosecutors, impeachment, giuliani
## Topic 3 Top Words:
## Highest Prob: pennsylvania, seat, carolina, endorsed, scott
## FREX: runoff, mail-in, hampshire, warnock, fetterman
## Lift: elissa, laxalt, nrsc, outraised, outspent
## Score: warnock, kemp, absentee, runoff, mail-in
## Topic 4 Top Words:
## Highest Prob: protests, obama, man, george, video
## FREX: floyd, protests, kennedy, confederate, bush
## Lift: portraits, song, confederate, knee, nfl
## Score: portraits, floyd, confederate, lady, protests
## Topic 5 Top Words:
## Highest Prob: cuomo, resign, mayor, sexual, santos
## FREX: santos, cuomo, ocasio-cortez, omar, blasio
## Lift: boylan, derosa, santos, tara, blasio
## Score: santos, cuomo, ocasio-cortez, blasio, sexual
You can view stm_model$theta to get the document-topic
probabilities. But STM also includes the findThoughts
command to help identify documents that are representative of a given
topic. Here, I’m getting a document that is representative of topic 6,
which appears to be related to immigration and border control issues
findThoughts(stm_model,
# list of texts (the dimnames part here ensures that the doc names line up with the names in the DFM)
texts = pol_corpus[pol_dfm@Dimnames[[1]]],
# topic 6
topics = 6 ,
n = 1)
##
## Topic 6:
## An investigator generalreport found that the U.S. Environmental Protection Agency (EPA) "improperly" doled out and managed millions of dollars worth of taxpayer dollars in information technologycontracts between 2017 and 2019. The EPA's Office of Inspector General reported Wednesday that the agency had spent $52.5 million in taxpayer money on the deals, which the watchdog had investigated following a complaint about "contract and bidding irregularities with three major information technology contracts." The report said the EPA violated "Federal Acquisition Regulation requirements and contract clauses" when they "purchased 23 pieces of hardware and software equipment" through an "expiring" IT contract with Canadian IT company CGI Federal. "This purchase was outside the scope of the contract and was ultimately never used for that contract," wrote the IG's office. "The EPA then improperly solicited bids for one of two subsequent contracts and transferred the equipment to use on the new contract." "By approving the purchase, the EPA improperly spent $641,680 in federal funds," the report continued. The report went on to reveal that the EPA "issued task orders" under the three contracts "without approval" from the agency's CIO –which the watchdog says is "required under the Federal Information Technology Acquisition Reform Act (FITARA)." These tasks orders "resulted in the EPA spending $52.5 million in taxpayer funds without proper approvals," the report read. "The agency also mismanaged these contracts with respect to monitoring property and licenses," the report continued. "For example, the EPA underreported and incorrectly identified purchased equipment in the agency's property reporting system and did not record $1.18 million in software licenses in the agency's asset management system." The IG said in the conclusion of the report that the EPA "CIO was unaware of significant acquisitions made by the agency and was unable to appropriately review price competitions, cost reductionsand duplicate equipment." "As a result, the EPA spent $52.5 million in taxpayer dollars without the CIO's oversight," the IG concluded. The watchdog recommended that the agency's assistant administrator for mission support "[r]eview all active contracts for acquisitions of information technology hardware, softwareand services in fiscal year 2016 and later" to see if the "required" FITARA approvals had been "obtained." The IG also called on the assistant administrator to "identify cost findings in the process from hardware and software purchases that were either duplicates or unnecessary" and to "[r]equire contracting officers to maintain records" of CIO approvals, "including those under" FITARA. When pressed for comment, a spokesperson for the EPA pointed Fox News to their response in the IG report, where the agency said it "agreed" with the IG's recommendations in the report. The confirmation ofPresident Joe Biden's pick to lead the EPA, Michael Regan, is up for a vote in the Senate this week.
The more important innovation here is that we can estimate the effect
of a covariate on the topic prevalence. Here, I’m estimating the effect
of source on the prevalence of all 15 topics (although I’m only showing
the results for topic 6)
fx <- estimateEffect(1:15 ~ source , stm_model,
meta = dfm2stm$meta)
# only showing topic 6 (remove the $tables[[6]] part to see all topics)
summary(fx)$tables[[6]]
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.046126691 0.003498798 13.18358 6.987575e-39
## sourceFox News 0.008347122 0.004820188 1.73170 8.340409e-02
We can also plot the results (although this is very messy for more
than a handful of topics). In either case, we see Fox is generally more
likely to cover the immigration topic compared to CNN.
plot.estimateEffect(fx,
covariate = "source",
topics = 1:6,
# value of fox news - cnn
cov.value1 = "Fox News",
cov.value2 = "CNN",
method='difference',
# you'll need to adjust this to keep the text from getting cut off
xlim = c(-.1, .1)
)

There’s also a (slightly janky) package called stminsights
that will allow you to do some of this interactively. This isn’t
something you can really share unless you upload it to a server
somewhere, but you can use it to generate and then download some plots
which you could use elsewhere.
# for some reason, the dfm data must be named "out" or else this doesn't work.
out<-dfm2stm
# save the relevant model outputs:
save(file='stm_model.RData', list = c('stm_model', 'out', 'fx', 'pol_corpus'))
library(stminsights)
# go to browse and find stm_model.Rdata to get this running
run_stminsights()
Keyword Assisted Topic Models
Keyword Assisted Topic models (see:
paper) are a new approach that allows both document covariates and
seed terms to define topics. In short: it combines the SeededLDA
approach with the Structural Topic Model. This is a pretty new method
and the package is still under development, but I’m including it here
because it might be of interest for those of you who are already using
dictionary based methods.
The setup for the basic model is almost identical to seeded LDA, the
main practical difference is that it automatically infers the \(\alpha\) and \(\beta\) parameters that control how diffuse
the \(\theta\) and \(\phi\) are, which can save you the effort
of having to tune these parameters.
We can use the same dictionary we implemented before. The keyATM
package also provides some additional options for checking the quality
of our selected keyword - notice that it throws a warning because some
of the terms in our keyword list don’t actually appear in the corpus.
Some terms were probably too common and got removed when we ran
dfm_trim, other terms may just be too rare to be
useful.
Although it will make the model run slower, you might want to
increase max_docfreq when you clean the data.
library(keyATM) # keyword assisted topic models
# our news dictionary
news_dict<-list('covid' = c('covid*', 'vaccin*', 'lockdown*'),
'natsec' = c('afghan*', 'taliban', 'putin',
'china', 'ukrain*','zelensky', 'terror*'),
'elections' = c('election', 'caucus', 'primary', 'debate*',
'nomination', 'midterm', 'vote*', 'poll*'),
'trump' = c('indictment', 'impeachment', 'mar-a-lago'),
'jan6' = c("capitol", 'january', 'chansley','fraud'),
'blm' = c('floyd' , 'black', 'police', "protest*", 'blm','black-lives-matter'),
'immigration' = c('immigrat*', 'border', 'homeland', 'migrant*'),
'climate' = c('global','climate*','fuel', 'carbon', 'emission*'),
'abortion' = c('abort*', 'roe', 'wade', 'fetus', 'dobbs'),
'economy' = c('inflation','price*', 'tax')
)
# converted to a quanteda dictionary
dict_topic<-dictionary(news_dict)
# convert the pol_dfm to a structure preferred by this package
keyATM_docs<-keyATM_read(pol_dfm)
# convert the quanteda dictionary to a format used by keyATM
keys<-read_keywords(docs = keyATM_docs, dictionary = dict_topic)
#visualize keywords:
key_viz<-visualize_keywords(docs = keyATM_docs, keywords = keys)
key_viz

And here’s the model training function. You’ll probably notice that
this is quite a bit slower than either the standard or seeded LDA. You
might want to get this running and go grab a snack. The authors
recommend saving the model after it is finished running, so I’m going to
do that here. To load it back up, you would just need to run
`klda_model <- readRDS('klda.rds')'.
klda_model <- keyATM(
docs = keyATM_docs, # text input
no_keyword_topics = 5, # 5 extra garbage topics
keywords = keys, # our list of terms
model = "base", # the base model
options = list(seed = 250) # set the random number seed inside the function
)
# save the trained model
saveRDS(klda_model, file='klda.rds')
You can get the highest probability words using the
top_words function. The resulting dataframe will have a
checkmark next to terms that were part of your keyword list.
top_words(klda_model)
Once again, we can use this to retrieve specific documents (and to
check the quality of our topics)
election_df<-docvars(pol_dfm)%>%
# add % of covid topic
mutate(election= klda_model$theta[,'3_elections'])%>%
arrange(-election)
election_df%>%
select(url)%>%
slice_head(n=10)
And we can use this to track news coverage over time. Here, we see
peaks in the “elections” topic that roughly coincide with presidential
general election seasons in the time period.
election_coverage<-election_df%>%
mutate(# round down month
month = floor_date(date, unit='months'))%>%
# group by month and compute the monthly average:
group_by(month)%>%
summarise(election= mean(election))
election_coverage%>%
ggplot(aes(x=month, y=election)) +
# create the line plot
geom_line() +
geom_point() +
ylab('Average topic %') +
# using the minimal theme:
theme_minimal() +
ggtitle('Average % election topic by month')

BERTopic
Lastly, while probably outside the scope of this course, I do want to
highlight a final variation of the topic model known as BERTopic paper. Rather than
inferring topics using a document-term-matrix, BERTopic uses an
embedding model - a kind of numeric representation of text that captures
semantic similarities between words, sentences or documents (good primer
on this concept here).
Importantly, we can use pre-existing embeddings that were trained on a
huge corpus and apply them to a new set of documents, which gives us a
major head-start when trying to train a model.
BERTopic generally works better on paragraphs or even single
sentences, so we are going to use the same dataset here, but only take
the first 5 sentences from each article. Other than that, we really
don’t need to do any pre-cleaning of the data because the sentence embedding model
we’re applying here was trained on regular human sentences and can use
word order and context clues when producing numeric representations of
sentences.
import pandas as pd
from nltk.tokenize import sent_tokenize, word_tokenize
from bertopic import BERTopic
from sentence_transformers import SentenceTransformer
# read the corpus
poldocs = pd.read_csv('https://github.com/Neilblund/APAN/raw/main/news_sample.csv')
# split the text into sentences
sentences = [sent_tokenize(text) for text in poldocs.text]
# get the first three sentences from each text
sentences = [' '.join(x[:4]) for x in sentences]
# apply a pre-trained embedding model on the sentences
embedding_model = SentenceTransformer("all-MiniLM-L6-v2")
embeddings = embedding_model.encode(sentences, show_progress_bar=False)
topic_model = BERTopic(embedding_model = embedding_model)
# train the model
topics, probs = topic_model.fit_transform(sentences, embeddings)
# save the results in the working directory
topic_model.save("./bertmodel", serialization="safetensors", save_ctfidf=True, save_embedding_model=embedding_model)
The bertopic package offers a number of options for visualizing our
results. The first plot shows an inter-topic distance map (similar to
the kind provided by LDAVis). Notably: the BERTopic model inferred a
much larger number of topics (~60) here than what we set in previous
iterations, and many appear to reflect a much finer level of detail.
from bertopic import BERTopic
# loading the saved model
loaded_model = BERTopic.load("./bertmodel")
# visualize topic similarities:
loaded_model.visualize_topics()
loaded_model.visualize_barchart(topics = [ 1,3, 4, 14, 17] )
# getting topic counts by source
classes = r.poldf.source
topics_per_class = loaded_model.topics_per_class(r.poldf.text, classes=classes)
# abortion, ukraine, hunter biden, gender, and jan 6
loaded_model.visualize_topics_per_class(topics_per_class, topics = [ 1,3, 4, 14, 17], normalize_frequency= True)
LS0tDQp0aXRsZTogIkFkZGl0aW9uYWwgdGV4dCBhbmFseXNpcyBpbmZvcm1hdGlvbiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIA0KDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFQpDQpMREF2aXNfc3RhbmRhbG9uZSA8LSBmdW5jdGlvbihtb2RlbCwgb3V0cHV0ZmlsZSkgew0KICByZXF1aXJlKExEQXZpcykNCiAgIyBtZXRob2QgZm9yIFdhcnBMREENCiAgaWYoY2xhc3MobW9kZWwpWzFdID09ICJXYXJwTERBIil7DQogICAganNvbiA9IGNyZWF0ZUpTT04oDQogICAgICBwaGkgPSBtb2RlbCQuX19lbmNsb3NfZW52X18kcHJpdmF0ZSR0b3BpY193b3JkX2Rpc3RyaWJ1dGlvbl93aXRoX3ByaW9yKCksDQogICAgICB0aGV0YSA9IG1vZGVsJC5fX2VuY2xvc19lbnZfXyRwcml2YXRlJGRvY190b3BpY19kaXN0cmlidXRpb25fd2l0aF9wcmlvcigpLA0KICAgICAgZG9jLmxlbmd0aCA9IG1vZGVsJC5fX2VuY2xvc19lbnZfXyRwcml2YXRlJGRvY19sZW4sDQogICAgICB2b2NhYiA9IG1vZGVsJC5fX2VuY2xvc19lbnZfXyRwcml2YXRlJHZvY2FidWxhcnksDQogICAgICB0ZXJtLmZyZXF1ZW5jeSA9IGNvbFN1bXMobW9kZWwkLl9fZW5jbG9zX2Vudl9fJHNlbGYkY29tcG9uZW50cyksDQogICAgICByZW9yZGVyLnRvcGljcyA9IEZBTFNFDQogICAgICANCiAgICApDQogIH0NCiAgIyBtZXRob2QgZm9yIHRleHRtb2RlbF9sZGENCiAgaWYoY2xhc3MobW9kZWwpWzFdID09ICd0ZXh0bW9kZWxfbGRhJyl7DQogICAganNvbjwtY3JlYXRlSlNPTigNCiAgICAgIHBoaSA9IG1vZGVsJHBoaSwNCiAgICAgIHRoZXRhID0gbW9kZWwkdGhldGEsDQogICAgICBkb2MubGVuZ3RoID0gcm93U3Vtcyhtb2RlbCRkYXRhKSwNCiAgICAgIHZvY2FiID0gY29sbmFtZXMobW9kZWwkZGF0YSksDQogICAgICB0ZXJtLmZyZXF1ZW5jeSA9IGNvbFN1bXMobW9kZWwkZGF0YSksDQogICAgICByZW9yZGVyLnRvcGljcyA9IEZBTFNFKQ0KICB9DQogIA0KICBpZihjbGFzcyhtb2RlbClbMV0gPT0gImtleUFUTV9vdXRwdXQiKXsNCiAgICBqc29uID0gY3JlYXRlSlNPTigNCiAgICAgIHBoaSA9IG1vZGVsJHBoaSwNCiAgICAgIHRoZXRhID0gbW9kZWwkdGhldGEsDQogICAgICBkb2MubGVuZ3RoID0gbW9kZWwkZG9jX2xlbnMsIA0KICAgICAgdm9jYWIgPSBtb2RlbCR2b2NhYiwNCiAgICAgIHRlcm0uZnJlcXVlbmN5ID0gbW9kZWwkd29yZF9jb3VudHMsDQogICAgICByZW9yZGVyLnRvcGljcyA9IEZBTFNFDQogICAgKQ0KICAgIA0KICB9DQogIA0KICANCiAgaWRfbmFtZTwtcGFzdGUwKCdlbGRhdmlzXycsIGdzdWIoIlteMC05XSIsICIiLCBhcy5udW1lcmljKFN5cy50aW1lKCkpKSwgY29sbGFwc2U9IiIpDQogIA0KICANCiAgY2F0KA0KICAgIHNwcmludGYoDQogICAgICAnPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiB0eXBlPSJ0ZXh0L2NzcyIgaHJlZj0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L2doL2JtYWJleS9weUxEQXZpc0AzLjQuMC9weUxEQXZpcy9qcy9sZGF2aXMudjEuMC4wLmNzcyI+DQoNCjxkaXYgaWQ9IiVzIiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsiPjwvZGl2Pg0KICAgIDxzY3JpcHQgdHlwZT0idGV4dC9qYXZhc2NyaXB0Ij4NCiAgICB2YXIgbGRhdmlzX2RhdGEgPSAlcw0KDQpmdW5jdGlvbiBMREF2aXNfbG9hZF9saWIodXJsLCBjYWxsYmFjayl7DQogIHZhciBzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudChcJ3NjcmlwdFwnKTsNCiAgcy5zcmMgPSB1cmw7DQogIHMuYXN5bmMgPSB0cnVlOw0KICBzLm9ucmVhZHlzdGF0ZWNoYW5nZSA9IHMub25sb2FkID0gY2FsbGJhY2s7DQogIHMub25lcnJvciA9IGZ1bmN0aW9uKCl7Y29uc29sZS53YXJuKCJmYWlsZWQgdG8gbG9hZCBsaWJyYXJ5ICIgKyB1cmwpO307DQogIGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJoZWFkIilbMF0uYXBwZW5kQ2hpbGQocyk7DQp9DQoNCmlmKHR5cGVvZihMREF2aXMpICE9PSAidW5kZWZpbmVkIil7DQogICAvLyBhbHJlYWR5IGxvYWRlZDoganVzdCBjcmVhdGUgdGhlIHZpc3VhbGl6YXRpb24NCiAgICFmdW5jdGlvbihMREF2aXMpew0KICAgICAgIG5ldyBMREF2aXMoIiMiICsgIiVzIiwgbGRhdmlzX2RhdGEpOw0KICAgfShMREF2aXMpOw0KfWVsc2UgaWYodHlwZW9mIGRlZmluZSA9PT0gImZ1bmN0aW9uIiAmJiBkZWZpbmUuYW1kKXsNCiAgIC8vIHJlcXVpcmUuanMgaXMgYXZhaWxhYmxlOiB1c2UgaXQgdG8gbG9hZCBkMy9MREF2aXMNCiAgIHJlcXVpcmUuY29uZmlnKHtwYXRoczoge2QzOiAiaHR0cHM6Ly9kM2pzLm9yZy9kMy52NSJ9fSk7DQogICByZXF1aXJlKFsiZDMiXSwgZnVuY3Rpb24oZDMpew0KICAgICAgd2luZG93LmQzID0gZDM7DQogICAgICBMREF2aXNfbG9hZF9saWIoImh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9naC9ibWFiZXkvcHlMREF2aXNAMy40LjAvcHlMREF2aXMvanMvbGRhdmlzLnYzLjAuMC5qcyIsIGZ1bmN0aW9uKCl7DQogICAgICAgIG5ldyBMREF2aXMoIiMiICsgIiVzIiwgbGRhdmlzX2RhdGEpOw0KICAgICAgfSk7DQogICAgfSk7DQp9ZWxzZXsNCiAgICAvLyByZXF1aXJlLmpzIG5vdCBhdmFpbGFibGU6IGR5bmFtaWNhbGx5IGxvYWQgZDMgJiBMREF2aXMNCiAgICBMREF2aXNfbG9hZF9saWIoImh0dHBzOi8vZDNqcy5vcmcvZDMudjUuanMiLCBmdW5jdGlvbigpew0KICAgICAgICAgTERBdmlzX2xvYWRfbGliKCJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvZ2gvYm1hYmV5L3B5TERBdmlzQDMuNC4wL3B5TERBdmlzL2pzL2xkYXZpcy52My4wLjAuanMiLCBmdW5jdGlvbigpew0KICAgICAgICAgICAgICAgICBuZXcgTERBdmlzKCIjIiArICIlcyIsIGxkYXZpc19kYXRhKTsNCiAgICAgICAgICAgIH0pDQogICAgICAgICB9KTsNCn0NCjwvc2NyaXB0PicsIGlkX25hbWUgLHBhc3RlKGpzb24pLCBpZF9uYW1lLCBpZF9uYW1lLCBpZF9uYW1lKSwgZmlsZSAgPSAgb3V0cHV0ZmlsZSl9DQoNCiNjdXN0b20gZnVuY3Rpb24gZm9yIHN0ZW1taW5nIGFuZCB0aGVuIHVuc3RlbW1pbmcgYSBkZm0NCnN0ZW1fdW5zdGVtPC1mdW5jdGlvbihkZm0sIG1hdGNoYnk9J2ZyZXF1ZW5jeScpew0KIGlmKG1hdGNoYnkgPT0gJ3Nob3J0ZXN0Jyl7DQogICAgdm9jYWI8LWNvbG5hbWVzKGRmbSlbb3JkZXIobmNoYXIoY29sbmFtZXMoZGZtKSksIGRlY3JlYXNpbmc9VCldDQogfQ0KIGlmKG1hdGNoYnkgPT0gJ2ZyZXF1ZW5jeScpew0KICAgICB2b2NhYjwtbmFtZXMoc29ydChmZWF0ZnJlcShkZm0pLCBkZWNyZWFzaW5nPVQpKQ0KDQogfQ0KICBzdGVtbWVkPC1jaGFyX3dvcmRzdGVtKHZvY2FiLCBjaGVja193aGl0ZXNwYWNlPUZBTFNFKQ0KICBkZm08LWRmbV9yZXBsYWNlKGRmbSwgdm9jYWIsIHN0ZW1tZWQpDQogIA0KICBkZm08LSBkZm1fcmVwbGFjZShkZm0sIGZlYXRuYW1lcyhkZm0pLCB2b2NhYlttYXRjaChmZWF0bmFtZXMoZGZtKSwgc3RlbW1lZCldKQ0KICByZXR1cm4oZGZtKQ0KfQ0KICAgICAgDQpgYGANCg0KDQojIFByZWFtYmxlOiBydW5uaW5nIHRoaXMgY29kZQ0KDQpUaGVyZSdzIGEgbG90IG9mIHBhY2thZ2VzIGJlaW5nIGxvYWRlZCBoZXJlISBJZiB5b3UgZG9uJ3QgaGF2ZSBhbnkgb2YgdGhlc2UgYWxyZWFkeSBhbmQgd2FudCB0byBydW4gdGhpcyBjb2RlLCB0aGVuIHlvdSdsbCBuZWVkIHRoZXNlLiBJZiB5b3Ugd2FudCB0byBlbWJlZCB0aGUgaW50ZXJhY3RpdmUgTERBIHZpc3VhbGl6YXRpb25zIHVzaW5nIHRoZSBjdXN0b20gZnVuY3Rpb25zIGRpc2N1c3NlZCBiZWxvdywgeW91J2xsIGFsc28gbmVlZCB0byBnZXQgdGhlIGRldmVsb3BtZW50IHZlcnNpb24gb2YgYExEQXZpc2AgZXZlbiBpZiB5b3UgZG93bmxvYWRlZCB0aGUgcmVndWxhciBDUkFOIHZlcnNpb24gZWFybGllci4gVGhlIGRldmVsb3BtZW50IHZlcnNpb24gZml4ZXMgYW4gaXNzdWUgd2l0aCB0aGUgdG9waWNzIGJlaW5nIG51bWJlcmVkIGRpZmZlcmVudGx5IGluIHRoZSB2aXN1YWxpemF0aW9uIHZzIGluIFIuIElmIHlvdSB3YW50IHRvIHJ1biB0aGUgQkVSVG9waWMgbW9kZWwgaW4gUHl0aG9uIHRoZW4geW91J2xsIGFsc28gbmVlZCB0byBpbnN0YWxsIGBwYW5kYXNgLCBgbmx0a2AsIGFuZCBgYmVydG9waWNgLiANCg0KVHJ5aW5nIHRvIGNvbXBpbGUgdGhpcyBlbnRpcmUgUk1EIGlzIHByb2JhYmx5IGdvaW5nIHRvIGJlIHZlcnkgc2xvdyBhbmQgbWF5YmUgd29uJ3Qgd29yayBhdCBhbGwuIFNvIGluc3RlYWQgb2YgZG9pbmcgdGhhdCwgSSB3b3VsZCByZWNvbW1lbmQgeW91IGp1c3QgdGFrZSBvdXQgdGhlIGNodW5rcyB5b3UgcmVhbGx5IHdhbnQgdG8gdXNlIGFuZCBydW4gdGhlbSBpbnRlcmFjdGl2ZWx5LiBJbiBnZW5lcmFsLCB0aGUgZGljdGlvbmFyeSBiYXNlZCBzdHVmZiBpcyBnb2luZyB0byBydW4gbXVjaCBmYXN0ZXIgdGhhbiB0aGUgdG9waWMgbW9kZWxzLiAgDQoNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQoNCiMjIyMgbm90ZSB0aGF0IGV2YWwgPSBmYWxzZSBmb3IgdGhpcyBjaHVuayAtIG9ubHkgcnVuIGl0IGlmIHlvdSBuZWVkIGl0ISANCg0KIyMjIyBhZGRpdGlvbmFsIHF1YW50ZWRhIHBhY2thZ2VzOiANCmluc3RhbGwucGFja2FnZXMoJ3F1YW50ZWRhLnRleHRtb2RlbHMnKQ0KaW5zdGFsbC5wYWNrYWdlcygncXVhbnRlZGEudGV4dHBsb3RzJykNCg0KIyMjIyBkZXZlbG9wbWVudCB2ZXJzaW9uIG9mIExEQXZpcyAtIGZpeGVzIGEgcHJvYmxlbSB3aXRoIHRvcGljIG9yZGVyaW5nDQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImNwc2lldmVydC9MREF2aXMiKQ0KIyMjI2FkZGl0aW9uYWwgZGljdGlvbmFyaWVzOiANCg0KZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJrYmVub2l0L3F1YW50ZWRhLmRpY3Rpb25hcmllcyIpIA0KcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoInF1YW50ZWRhL3F1YW50ZWRhLnNlbnRpbWVudCIpDQojIyNTZWVkZWQgTERBIA0KDQppbnN0YWxsLnBhY2thZ2VzKCJzZWVkZWRsZGEiKQ0KIyMjIFN0cnVjdHVyYWwgdG9waWMgbW9kZWwgDQppbnN0YWxsLnBhY2thZ2VzKCJzdG0iKQ0KIyMjIGFkZGl0aW9uYWwgcGFja2FnZSBmb3Igdmlld2luZyBTVE0gb3V0cHV0OiANCmluc3RhbGwucGFja2FnZXMoInN0bWluc2lnaHRzIikNCg0KIyMjS2V5d29yZCBMREENCmluc3RhbGwucGFja2FnZXMoIktleUFUTSIpDQoNCg0KYGBgDQoNCiMgU2V0dXA6IGNsZWFuaW5nIHRoZSBkYXRhDQoNClNpbmNlIG1vc3Qgb2YgdGhlc2UgbW9kZWxzIGFyZSBnb2luZyB0byB1c2UgdGhlICJiYWctb2Ytd29yZHMiIGFzc3VtcHRpb24sIHdlIGNhbiBkbyB0aGUgc2FtZSBjbGVhbmluZyBzdGVwcyBmb3IgZWFjaC4gVGhlIGNvZGUgYmVsb3cgc2hvdWxkIGJlIGZhbWlsaWFyIHRvIHlvdSBhbHJlYWR5LCBhbmQgZHJhd3MgZnJvbSBhIGNvcnB1cyBvZiBuZXdzIHN0b3JpZXMgc2NyYXBlZCBmcm9tIENOTiBBTkQgRm94IE5ld3MgZnJvbSAyMDIwIHRocm91Z2ggU2VwdGVtYmVyIDIwMjMuIFRoZSAidGV4dCIgY29sdW1uIGNvbnRhaW5zIHRoZSB0ZXh0LCBhbmQgdGhlICJzb3VyY2UiIGNvbHVtbiBjb250YWlucyB0aGUgc291cmNlLiBUaGVyZSBhcmUgYWxzbyBkYXRlcyBhc3NvY2lhdGVkIHdpdGggZWFjaCBhcnRpY2xlLiANCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICMgZm9yIGRhdGEgbWFuaXB1bGF0aW9uDQpsaWJyYXJ5KHF1YW50ZWRhKSAgIyBmb3IgY2xlYW5pbmcgYW5kIGFuYWx5c2lzIG9mIHRleHQNCmxpYnJhcnkocXVhbnRlZGEudGV4dHN0YXRzKSAjIGV4dHJhIHF1YW50ZWRhIGNvbW1hbmRzDQpsaWJyYXJ5KHF1YW50ZWRhLnRleHRwbG90cykgIyBmb3IgcGxvdHRpbmcgdGhlIGtleW5lc3Mgc3RhdGlzdGljDQoNCg0KcG9sZGY8LXJlYWRfY3N2KCdodHRwczovL2dpdGh1Yi5jb20vTmVpbGJsdW5kL0FQQU4vcmF3L21haW4vbmV3c19zYW1wbGUuY3N2JykNCg0KIyBub3JtYWxpemluZyBzb21lIGFwb3N0cm9waGVzIGFuZCBxdW90ZXMgKHN1cnByaXNpbmdseSBpbXBvcnRhbnQhKQ0KcG9sZGYkdGV4dDwtc3RyX3JlcGxhY2VfYWxsKHBvbGRmJHRleHQsIlx1MjAxQ3xcdTIwMUQiLCAnIicpJT4lDQogIHN0cl9yZXBsYWNlX2FsbCguLCJcdTIwMTl8XHUyMDE4IiwgIiciKQ0KIyBjb252ZXJ0IHRvIGEgY29ycHVzDQpwb2xfY29ycHVzPC1wb2xkZiU+JQ0KICB3aXRoKC4sY29ycHVzKHRleHQsIA0KICAgICAgICAgICAgICAgIGRvY3ZhcnMgPSBkYXRhLmZyYW1lKHVybCwgaGVhZGxpbmUsICBkYXRlLCBzb3VyY2UpKSkNCg0KIyB0b2tlbml6ZSB0aGUgdGV4dHMNCnBvbF90b2tlbnM8LXRva2Vucyhwb2xfY29ycHVzLCANCiAgICAgICAgICAgICAgICAgICB3aGF0ID0gJ3dvcmQnLA0KICAgICAgICAgICAgICAgICAgICMgdGhpcyBrZWVwcyBjaGFyYWN0ZXJpc3RpY3MgYWJvdXQgdGhlIGRvY3VtZW50cyBsaWtlIHRoZSBoZWFkbGluZSBhbmQgdXJsDQogICAgICAgICAgICAgICAgICAgcmVtb3ZlX3B1bmN0ID1UUlVFLA0KICAgICAgICAgICAgICAgICAgIHJlbW92ZV9zeW1ib2xzID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICByZW1vdmVfbnVtYmVycyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgcmVtb3ZlX3VybCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgcmVtb3ZlX3NlcGFyYXRvcnMgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfZG9jdmFycyA9IFRSVUUNCiAgICAgICAgICAgICAgICAgICApDQojIHJlbW92ZSBzdG9wIHdvcmRzIA0KdG9rc19ub3N0b3AgPC0gdG9rZW5zX3NlbGVjdChwb2xfdG9rZW5zLCBwYXR0ZXJuID0gc3RvcHdvcmRzKCJlbiIpLCBzZWxlY3Rpb24gPSAicmVtb3ZlIikNCiMgY29udmVydCB0byBhIGRvY3VtZW50LWZlYXR1cmUgbWF0cml4DQpwb2xfZGZtPC0gZGZtKHRva3Nfbm9zdG9wKQ0KIyBzdGVtIGFuZCB0aGVuIHVuc3RlbSB3b3JkcyAodXNpbmcgY3VzdG9tIGZ1bmN0aW9uIGluIHByZWFtYmxlIHRvIHRoaXMgZG9jdW1lbnQpDQpwb2xfZGZtPC1zdGVtX3Vuc3RlbShwb2xfZGZtKSANCiMgaWYgeW91IGRvbid0IHdhbnQgdG8gdXNlIHRoZSBzdGVtX3Vuc3RlbSBmdW5jdGlvbiwgcnVuIHRoaXMgaW5zdGVhZDogIA0KIyBwb2xfZGZtPC1kZm1fd29yZHN0ZW0oZGZtKSAgDQoNCiMgcmVtb3ZlIGZlYXR1cmVzIHRoYXQgb2NjdXIgbGVzcyB0aGFuIDEwIHRpbWVzDQpwb2xfZGZtPC1kZm1fdHJpbShwb2xfZGZtLCBtaW5fZG9jZnJlcT0gMTApDQojIHJlbW92ZSBmZWF0dXJlcyB0aGF0IG9jY3VyIGluIG1vcmUgdGhhbiAxMCUgb2YgZG9jdW1lbnRzIA0KIyBpbmNyZWFzaW5nIG1heF9kb2NmcmVxIG1heSBpbXByb3ZlIHJlc3VsdHMsIGJ1dCBpdCB3aWxsIHRha2UgbG9uZ2VyIGZvciBtb2RlbHMgdG8gY29udmVyZ2UNCnBvbF9kZm08LSBkZm1fdHJpbShwb2xfZGZtLCBtYXhfZG9jZnJlcSA9IDAuMSwgZG9jZnJlcV90eXBlID0gInByb3AiKQ0KYGBgDQoNCg0KDQojIEtleXdvcmRzIGluIGNvbnRleHQgDQoNCktXSUMgc3RhbmRzIGZvciAia2V5d29yZHMgaW4gY29udGV4dCIuIFdlIGNhbiB1c2UgS1dJQyB0byBmaW5kIHN0b3JpZXMgdGhhdCBjb250YWluIGEgdGVybSwgYW5kIGdldCB0aGUgdGV4dCBzdXJyb3VuZGluZyB0aGF0IHdvcmQuIFRoaXMgY29kZSB3b3VsZCBwdWxsIGFsbCBkb2N1bWVudHMgY29udGFpbmluZyB0ZXJtcyB0aGF0IHN0YXJ0ZWQgd2l0aCAiaW1taWciIChzbzogaW1taWdyYW50LCBpbW1pZ3JhdGlvbiwgaW1taWdyYW50cyBldGMuKSBhbG9uZyB3aXRoIGEgd2luZG93IG9mIDMgd29yZHMgb24gZWl0aGVyIHNpZGUuIA0KDQoobm90ZSB0aGF0IHRoaXMgaXMgYWN0dWFsbHkgcHVsbGluZyBmcm9tIHRoZSBgYHBvbF90b2tlbnNgYCBvYmplY3QgcmF0aGVyIHRoYW4gdGhlIERGTSwgdGhpcyBhcHByb2FjaCBkb2Vzbid0IHJlYWxseSByZXF1aXJlIGFueSBjbGVhbmluZyB0byB1c2UpDQoNCmBgYHtyfQ0KIyBrZXl3b3JkcyBpbiBjb250ZXh0DQprd19pbW1pZyA8LSBrd2ljKHBvbF90b2tlbnMsIHBhdHRlcm4gPSAgImltbWlnKiIsIHdpbmRvdz0zKQ0KIyB2aWV3IHRoZSBmaXJzdCAxMA0KaGVhZChrd19pbW1pZywgNSkNCmBgYA0KDQpGb3IgeW91ciBvd24gd29yaywgeW91IG1heSB3YW50IHRvIGdldCBtb3JlIGNvbnRleHQgYnkgaW5jcmVhc2luZyB0aGUgYGB3aW5kb3dgYCBhcmd1bWVudCBpbiB0aGUgS1dJQyBjb21tYW5kLiBZb3UgbWlnaHQgYWxzbyBjb25zaWRlciB3cml0aW5nIHRoZXNlIHJlc3VsdHMgdG8gYSBjc3YgZmlsZSBzbyB5b3UgY2FuIHJlYWQgdGhlbSBpbiBhIHNwcmVhZHNoZWV0LiBLV0lDIGRvZXNuJ3QgYXV0b21hdGljYWxseSBwcm92aWRlIHlvdSB3aXRoIG5pY2UgbG9va2luZyBwbG90cywgYnV0IGl0IGNhbiBiZSBhIHZlcnkgZ29vZCB3YXkgdG8gZG8gdGhpbmdzIGxpa2UgZGV2ZWxvcCBhIGRpY3Rpb25hcnkgb3IgZ2V0IGEgZ2VuZXJhbCBxdWFsaXRhdGl2ZSBzZW5zZSBvZiBob3cgc291cmNlcyBhcmUgZGlzY3Vzc2luZyBzb21ldGhpbmcuDQoNCiMgIEtleW5lc3MNCg0KS2V5bmVzcyBpcyBhIG1lYXN1cmUgb2YgcmVsYXRpdmUgZnJlcXVlbmN5IHRoYXQgY2FuIGJlIHVzZWZ1bCBmb3IgZmluZGluZyB0ZXJtcyB0aGF0IGFyZSBtb3JlIGNvbW1vbiBpbiBwYXJ0aWN1bGFyIHN1YnNldHMgb2YgZG9jdW1lbnRzLiBGb3IgaW5zdGFuY2UsIGlmIHdlIHdhbnQgdG8gZmluZCB0ZXJtcyB0aGF0IGFyZSBtb3JlIGNvbW1vbiBpbiBGb3ggTmV3cyBhcnRpY2xlcyBjb21wYXJlZCB0byBDTk4sIHdlIGNvdWxkIGp1c3QgcnVuIHRoZSBmb2xsb3dpbmc6IA0KDQpgYGB7cn0NCg0KdHN0YXRfa2V5IDwtIHRleHRzdGF0X2tleW5lc3MocG9sX2RmbSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGZpbmQgZGlzdGluY3RpdmUgdGVybXMgZm9yIEZveCBuZXdzIGNvbXBhcmVkIHRvIENOTiA6DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBkb2N2YXJzKHBvbF9kZm0pJHNvdXJjZSA9PSAiRm94IE5ld3MiKQ0KdGV4dHBsb3Rfa2V5bmVzcyh0c3RhdF9rZXkpDQoNCmBgYA0KDQoNCldlIGNvdWxkIGFsc28gZm9jdXMgb24gYSBzdWJzZXQgb2YgYXJ0aWNsZXMgdG8gc2VlIGhvdyBzb3VyY2VzIGRpZmZlciBpbiB0aGVpciBjb3ZlcmFnZSBvZiBzaW1pbGFyIHRvcGljcy4gSGVyZSwgd2UncmUgbG9va2luZyBhdCBrZXl3b3JkcyB0aGF0IGFyZSBzdHJvbmdseSBhc3NvY2lhdGVkIHdpdGggZG9jdW1lbnRzIHRoYXQgbWVudGlvbiBIdW50ZXIgQmlkZW4uIFRoZSByZXN1bHRzIHN1Z2dlc3QgdGhhdCBGb3ggYW5kIENOTiBoYXZlIHNvbWUgdmVyeSBkaXN0aW5jdCB0ZXJtaW5vbG9neSB3aGVuIGRpc2N1c3NpbmcgdGhpcyBpc3N1ZTogIA0KDQpgYGB7cn0NCmh1bnRlcl9kZm08LWRmbV9zdWJzZXQocG9sX2RmbSwgDQogICAgICAgICAgICAgICAgICAgICAgICMgIGdldCBhIHN1YnNldCBvZiBhcnRpY2xlcyB0aGF0IG1lbnRpb24gSHVudGVyIEJpZGVuDQogICAgICAgICAgICAgICAgICAgICAgIHN1YnNldCAgPSBzdHJfZGV0ZWN0KHBvbF9jb3JwdXMsICJIdW50ZXIgQmlkZW4iKSkNCnRzdGF0X2tleSA8LSB0ZXh0c3RhdF9rZXluZXNzKGh1bnRlcl9kZm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBkb2N2YXJzKGh1bnRlcl9kZm0pJHNvdXJjZSA9PSAiRm94IE5ld3MiKQ0KdGV4dHBsb3Rfa2V5bmVzcyh0c3RhdF9rZXkpDQoNCmBgYA0KDQoNCiMgRGljdGlvbmFyeSBNZXRob2RzDQoNCldlIGNhbiB1c2UgdGhlIGRpY3Rpb25hcnkgYXBwcm9hY2ggdG8gc2ltcGx5IG1hdGNoIHRlcm1zIGFnYWluc3QgYSBkaWN0aW9uYXJ5IG9mIHRlcm1zIHdlJ3JlIGludGVyZXN0ZWQgaW4uIFRoZSBwb2xpY3kgYWdlbmRhcyBkaWN0aW9uYXJ5IGNvbnRhaW5zIGEgbGlzdCBvZiB0ZXJtcyBhc3NvY2lhdGVkIHdpdGggbWFqb3IgcG9saWN5IGFyZWFzLiBIZXJlLCB3ZSBhcHBseSB0aGUgYWdlbmRhcyBjb2RlYm9vayB0byB0aGUgdG9rZW5zIGxpc3QsIHdoaWNoIHJlbW92ZXMgYWxsIHRlcm1zIHRoYXQgYXJlbid0IHBhcnQgb2YgdGhlIGRpY3Rpb25hcnkuIA0KDQpgYGB7cn0NCiMgZG93bmxvYWQgdGhlIHBvbGljeSBhZ2VuZGFzIGNvZGVib29rIChkaXNjdXNzZWQgaGVyZSAjaHR0cHM6Ly93d3cuYWxtZW5kcm9uLmNvbS90cmlidW5hL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE3LzA1L0NBUDIwMTN2Mi5wZGYpDQpwb2xpY3lBZ2VuZGFzPC1yZWFkUkRTKHVybCgnaHR0cHM6Ly9naXRodWIuY29tL05laWxibHVuZC9BUEFOL3Jhdy9tYWluL3BvbGljeV9hZ2VuZGFzX2RpY3Rpb25hcnkucmRzJykpDQoNCmltbWlnX3Rva2VuczwtdG9rZW5zX3N1YnNldChwb2xfdG9rZW5zLCANCiAgICAgICAgICAgICAgICAgICAgICAgIyAgZ2V0IGEgc3Vic2V0IG9mIGFydGljbGVzIHRoYXQgbWVudGlvbiBIdW50ZXIgQmlkZW4NCiAgICAgICAgICAgICAgICAgICAgICAgc3Vic2V0ICA9IHN0cl9kZXRlY3QocG9sX2NvcnB1cywgImltbWlncioiKSkNCg0KIyBjb252ZXJ0IHRvIGEgcXVhbnRlZGEgZGljdGlvbmFyeSBmb3JtYXQNCnBvbGljeWFnZW5kYXMuZGljdCA8LSBkaWN0aW9uYXJ5KHBvbGljeUFnZW5kYXMpDQoNCiMgYXBwbHkgdGhlIGRpY3Rpb25hcnkNCmFnZW5kYV9wb2xpdGljcyA8LSB0b2tlbnNfbG9va3VwKHBvbF90b2tlbnMsIGRpY3Rpb25hcnkgPSBwb2xpY3lhZ2VuZGFzLmRpY3QsIGxldmVscyA9IDEpDQoNCiMgY2hlY2sgb3V0IHRoZSBmaXJzdCByZXN1bHQ6IA0KYWdlbmRhX3BvbGl0aWNzW1sxXV0NCg0KYGBgDQoNCg0KV2UgY2FuIGNvbnZlcnQgdGhpcyBsaXN0IG9mIHRva2VucyB0byBhIGRvY3VtZW50IGZlYXR1cmUgbWF0cml4LiBBIERGTSB3aWxsIGhhdmUgb25lIHJvdyBwZXIgZG9jdW1lbnQgYW5kIG9uZSBjb2x1bW4gcGVyIGZlYXR1cmUsIHRoZSBjZWxscyB3aWxsIGNvbnRhaW4gY291bnRzIG9mIHRoZSBudW1iZXIgb2YgdGltZXMgYSBnaXZlbiBmZWF0dXJlIG9jY3VycyBpbiBlYWNoIGRvY3VtZW50LiBJbiB0aGlzIGNhc2UsIG91ciBERk0gd2lsbCBoYXZlIG9uZSBjb2x1bW4gZm9yIGVhY2ggb2YgdGhlIHRvcGljcyBpbiB0aGUgcG9saWN5IGFnZW5kYXMgZGljdGlvbmFyeS4gV2UgY2FuIHVzZSB0aGlzIHRvIGRvIHRoaW5ncyBsaWtlIGNvdW50IHRoZSBudW1iZXIgb2YgZG9jdW1lbnRzIHdoaWNoIGFyZSBhYm91dCBzb21lIHRvcGljLiANCg0KDQpgYGB7cn0NCiMgY29udmVydCB0byBhIGRmbQ0KYWdlbmRhX2RmbTwtZGZtKGFnZW5kYV9wb2xpdGljcykNCiMgbG9vayBhdCB0aGUgZmlyc3QgZmV3IHJvd3MNCmhlYWQoYWdlbmRhX2RmbSwgbj01KQ0KYGBgDQoNCmBgdGV4dHN0YXRfZnJlcXVlbmN5YGAgZ2l2ZXMgdXMgc29tZSBiYXNpYyBzdGF0cyBhYm91dCBvdXIgZG9jdW1lbnQgZmVhdHVyZXMuIFRoZSAiZnJlcXVlbmN5IiBjb2x1bW4gaXMgdGhlIG51bWJlciBvZiB0aW1lcyBhIHBvbGljeSBhZ2VuZGEgdG9waWMgb2NjdXJzLiBUaGUgImRvY2ZyZXEiIGNvbHVtbiBpcyB0aGUgbnVtYmVyIG9mIGRvY3VtZW50cyB3aGljaCBjb250YWluIHRoYXQgYWdlbmRhIHRvcGljIChyZWdhcmRsZXNzIG9mIHdoZXRoZXIgaXQgb2NjdXJzIG11bHRpcGxlIHRpbWVzIGluIGEgc2luZ2xlIGRvY3VtZW50KS4gRnJvbSB0aGlzLCB3ZSBjYW4gc2VlIHRoYXQgImhlYWx0aGNhcmUiIGlzIHRoZSBtb3N0IGNvbW1vbmx5IG9jY3VycmluZyBhZ2VuZGEgdG9waWMuIA0KDQpgYGB7cn0NCmFnZW5kYV9mcmVxPC10ZXh0c3RhdF9mcmVxdWVuY3koYWdlbmRhX2RmbSkNCmhlYWQoYWdlbmRhX2ZyZXEpDQpgYGANCg0KUHV0IHRoZW0gYWxsIGluIGEgaG9yaXpvbnRhbCBiYXJwbG90OiANCg0KYGBge3IgYWdlbmRhcGxvdH0NCmFnZW5kYV9mcmVxJT4lDQogIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoZmVhdHVyZSwgZG9jZnJlcSksIHk9ZG9jZnJlcSkpICsgDQogIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JywgZmlsbD0nbGlnaHRibHVlJykgKw0KICB4bGFiKCdwb2xpY3kgYWdlbmRhJykgKyANCiAgeWxhYignbnVtYmVyIG9mIGRvY3VtZW50cyBtZW50aW9uaW5nJykgKw0KICBjb29yZF9mbGlwKCkgKw0KICAjIHVzaW5nIHRoZSBtaW5pbWFsIHRoZW1lOiANCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgZ2d0aXRsZSgnRG9jdW1lbnQgZnJlcXVlbmN5IG9mIHBvbGljeSBhZ2VuZGEgdGVybXMgaW4gQ05OIHBvbGl0aWNzIGFydGljbGVzJykNCmBgYA0KDQoNCiMjIEFkZGl0aW9uYWwgZGljdGlvbmFyaWVzL2NvbW1hbmRzDQoNClF1YW50ZWRhIGRvZXMgaGF2ZSBzb21lIGFkZGl0aW9uYWwgZGljdGlvbmFyaWVzIHRoYXQgbWF5IGJlIG9mIHVzZSBmb3Igc29tZSBvZiB5b3UuIFRoZXNlIGNhbiBiZSBhY2Nlc3NlZCB0aHJvdWdoIHRoZSBbUXVhbnRlZGEuZGljdGlvbmFyaWVzXShodHRwczovL2dpdGh1Yi5jb20va2Jlbm9pdC9xdWFudGVkYS5kaWN0aW9uYXJpZXMpIHBhY2thZ2UgYW5kIHRoZSBbcXVhbnRlZGEuc2VudGltZW50XShodHRwczovL2dpdGh1Yi5jb20vcXVhbnRlZGEvcXVhbnRlZGEuc2VudGltZW50KSBwYWNrYWdlLiBBbHRob3VnaCB0aGVzZSBkb24ndCBhcHBlYXIgdG8gYmUgb24gQ1JBTiB5ZXQsIHlvdSBjYW4gaW5zdGFsbCB0aGVtIGJ5IHJ1bm5pbmcgdGhlIGNvbW1hbmQgYmVsb3csIGFuZCB0aGVuIGxvYWRpbmcgdGhlbSB0aGUgd2F5IHlvdSB3b3VsZCBsb2FkIGFueSBvdGhlciBsaWJyYXJ5LiBUaGUgYXNzb2NpYXRlZCBnaXRodWIgcGFnZSBoYXMgaW5mb3JtYXRpb24gb24gdGhlIGluY2x1ZGVkIGRpY3Rpb25hcnkgZmlsZXMuIA0KDQoNCg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1Yigia2Jlbm9pdC9xdWFudGVkYS5kaWN0aW9uYXJpZXMiKSANCnJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJxdWFudGVkYS9xdWFudGVkYS5zZW50aW1lbnQiKQ0KYGBgDQoNCg0KVGhlIFt0ZXh0c3RhdF9wb2xhcml0eV0oaHR0cHM6Ly9yZHJyLmlvL2dpdGh1Yi9xdWFudGVkYS9xdWFudGVkYS5zZW50aW1lbnQvbWFuL3RleHRzdGF0X3BvbGFyaXR5Lmh0bWwpIGZ1bmN0aW9uIHdpbGwgYXV0b21hdGljYWxseSBhcHBseSBhIGRpY3Rpb25hcnkgYW5kIG1lYXN1cmUgc2VudGVuY2UgcG9sYXJpdHkgYnkgZ2V0dGluZyB0aGUgbG9nIG9mIHBvc2l0aXZlL25lZ2F0aXZlIHRlcm1zIGluIHRoZSB0ZXh0LiBUaGUgW3RleHRzdGF0X3ZhbGVuY2VdKGh0dHBzOi8vcmRyci5pby9naXRodWIvcXVhbnRlZGEvcXVhbnRlZGEuc2VudGltZW50L21hbi90ZXh0c3RhdF92YWxlbmNlLmh0bWwpIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHRvIGNvbXB1dGUgc2NvcmVzIGZvciBkaWN0aW9uYXJpZXMgdGhhdCBoYXZlIG11bHRpcGxlIGNhdGVnb3JpZXMgcmF0aGVyIHRoYW4ganVzdCBwb3NpdGl2ZSB2cy4gbmVnYXRpdmUuDQoNCkhlcmUncyBhbiBleGFtcGxlIG9mIGhvdyB5b3UgbWlnaHQgdXNlIHRoZSBgdGV4dHN0YXRfcG9sYXJpdHlgIGNvbW1hbmQgZnJvbSB0aGlzIHBhY2thZ2UgYWxvbmcgd2l0aCB0aGUgW0xleGljb2RlciBTZW50aW1lbnQgRGljdGlvbmFyeV0oaHR0cHM6Ly93d3cuc25zb3Jva2EuY29tL2RhdGEtbGV4aWNvZGVyLykuIEluIHRoaXMgZXhhbXBsZSwgd2UncmUgbWVhc3VyaW5nIHRoZSBkb2N1bWVudCBwb2xhcml0eSwgdGhlbiBncmFwaGluZyB0aGUgYXZlcmFnZSBzZW50aW1lbnQgZm9yIGRvY3VtZW50cyBmb3IgZWFjaCBzb3VyY2UgYW5kIGRlcGVuZGluZyBvbiB3aGV0aGVyIHRoZXkgbWVudGlvbiBpbW1pZ3JhdGlvbi4gVGhlIHJlc3VsdHMgc3VnZ2VzdCB0aGF0IEZveCBOZXdzIHRlbmRzIHRvIGdpdmUgbW9yZSBuZWdhdGl2ZSBjb3ZlcmFnZSB3aGVuIGRpc2N1c3NpbmcgaW1taWdyYXRpb24gY29tcGFyZWQgdG8gdGhlaXIgY292ZXJhZ2Ugb2Ygb3RoZXIgdG9waWNzLCB3aGVyZSBhcyBDTk4gY292ZXJzIGltbWlncmF0aW9uIHdpdGggYSBzbGlnaHRseSBtb3JlIHBvc2l0aXZlIHZhbGVuY2UuIA0KDQpgYGB7cn0NCmxpYnJhcnkocXVhbnRlZGEuc2VudGltZW50KQ0KIyBsb2FkIHRoZSBsZXhpY29kZXIgc2VudGltZW50IGRpY3Rpb25hcnkNCmxzZDwtcXVhbnRlZGEuc2VudGltZW50OjpkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNQ0KDQpwb2xhcml0eTwtdGV4dHN0YXRfcG9sYXJpdHkocG9sX2NvcnB1cywgZGljdGlvbmFyeT1sc2QpJT4lDQogICMgYWRkIHBvbGFyaXR5IHRvIGV4aXN0aW5nIGRvY3VtZW50IHZhcmlhYmxlcw0KICBiaW5kX2NvbHMoZG9jdmFycyhwb2xfY29ycHVzKSklPiUNCiAgIyBhbHNvIGFkZCBhIGNvbHVtbiBmb3IgbWVudGlvbnMgb2YgaW1taWdyYXRpb24NCiAgbXV0YXRlKG1lbnRpb25zX2ltbWlncmF0aW9uID0gc3RyX2RldGVjdChwb2xfY29ycHVzLCJpbW1pZ3IqIikpDQoNCiMgcGxvdCBwb2xhcml0eSBmb3IgYWxsIGZvdXIgZ3JvdXBzDQpnZ3Bsb3QocG9sYXJpdHksIGFlcyh4PXNvdXJjZSwgeT1zZW50aW1lbnQsIGZpbGw9bWVudGlvbnNfaW1taWdyYXRpb24pKSArIA0KICBnZW9tX2JveHBsb3Qobm90Y2g9VFJVRSkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyB0ZXN0IGFzIGFuIGludGVyYWN0aW9uIHRlcm06DQojIyBzdW1tYXJ5KGxtKHNlbnRpbWVudCB+IHNvdXJjZSAqIG1lbnRpb25zX2ltbWlncmF0aW9uLCBkYXRhPXBvbGFyaXR5KSkNCg0KYGBgDQojIyBNYWtpbmcgYSBjdXN0b20gZGljdGlvbmFyeQ0KDQpGaW5hbGx5LCBrZWVwIGluIG1pbmQgdGhhdCB5b3UgY2FuIGNyZWF0ZSBhIGRpY3Rpb25hcnkgZnJvbSBhIG5hbWVkIGxpc3QuIFNvIGlmIHlvdSB3YW50IHRvIGNyZWF0ZSB5b3VyIG93biBmb3IgYSBzcGVjaWZpYyBkb21haW4gb3IgbGFuZ3VhZ2UsIG9yIGV4dGVuZCBhbiBleGlzdGluZyBvbmUsIHRoZSBzeW50YXggc2hvdWxkIGJlIHJlYXNvbmFibHkgc3RyYWlnaHRmb3J3YXJkLiANCg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmN1c3RvbV9saXN0PC1saXN0KENPVklEICA9YygnY292aWQnLCdjb3JvbmF2aXJ1cycsICd2YWNjaW4qJywgJ2xvY2sgZG93bicpLA0KICAgICAgICAgICAgICAgICAgSmFuNiA9IGMoICJqYW4qIDYqIiwgImNhcGl0b2wiLCAnaW5zdXJyZWN0aW9uKicpKQ0KDQoNCg0KY3VzdG9tX2RpY3QgPC0gZGljdGlvbmFyeShjdXN0b21fbGlzdCkNCnRvazwtdG9rZW5zX2xvb2t1cChwb2xfdG9rZW5zLCBkaWN0aW9uYXJ5PWN1c3RvbV9kaWN0KQ0KDQoNCnRhYmxlKHVubGlzdCh0b2spKQ0KDQpgYGANCg0KDQpBbnkgb2YgdGhlc2UgYXBwcm9hY2hlcyBhcmUgYSBnb29kIHN0YXJ0aW5nIHBvaW50IGFuZCBJIHRoaW5rIHlvdSBjYW4gZGVmaW5pdGVseSB1c2UgdGhlbSBmb3IgeW91ciBhbmFseXNlcywgYnV0IHlvdSBzaG91bGQgYmUgd2lsbGluZyB0byBhZG1pdCB0aGVpciBsaW1pdGF0aW9ucyB3aGVuIGFwcGxpZWQgb3V0c2lkZSB0aGUgYXJlYSB0aGV5IHdlcmUgZGVzaWduZWQgZm9yLiBDaGVjayBvdXQgW3RoaXMgcGFwZXJdKGh0dHBzOi8vd3d3LmNhbWJyaWRnZS5vcmcvY29yZS9qb3VybmFscy9wb2xpdGljYWwtYW5hbHlzaXMvYXJ0aWNsZS90ZXh0LWFzLWRhdGF0aGUtcHJvbWlzZS1hbmQtcGl0ZmFsbHMtb2YtYXV0b21hdGljLWNvbnRlbnQtYW5hbHlzaXMtbWV0aG9kcy1mb3ItcG9saXRpY2FsdGV4dHMvRjdBQUM4QjI5MDk0NDE2MDNGRUIyNUMxNTY0NDhGMjApIGZvciBzb21lIGRpc2N1c3Npb24gb2YgdGhlIGxpbWl0YXRpb25zIG9mIGRpY3Rpb25hcnkgYmFzZWQgYXBwcm9hY2hlcy4gDQoNCg0KIyBUb3BpYyBNb2RlbGluZw0KDQojIyBMREENCg0KTGF0ZW50IERpcmljaGxldCBBbGxvY2F0aW9uIChMREEpIGFsc28ga25vd24gYXMgdG9waWMgbW9kZWxpbmcgaXMgd2lkZWx5IHVzZWQgYXBwcm9hY2ggZm9yIHVuc3VwZXJ2aXNlZCBkb2N1bWVudCBjbGFzc2lmaWNhdGlvbi4gIlVuc3VwZXJ2aXNlZCIgaW4gdGhpcyBjYXNlLCBtZWFucyB0aGF0IHlvdSBkb24ndCBuZWVkIGFueSBwcmlvciBrbm93bGVkZ2UgYWJvdXQgdGhlIGNvcnB1cyBpbiBvcmRlciB0byBtYWtlIHVzZSBvZiBpdC4gSW5zdGVhZCwgYW4gTERBIG1vZGVsIHdpbGwgYXR0ZW1wdCB0byBtb2RlbCBkb2N1bWVudHMgYXMgYXMgYSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24gb3ZlciB0b3BpY3MsIGFuZCB0b3BpY3MgYXMgYSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24gb3ZlciB3b3Jkcy4gVGhpcyBpcyBkb25lIGF1dG9tYXRpY2FsbHkgLSBhbGwgeW91IHJlYWxseSBuZWVkIHRvIGRvIGlzIGNsZWFuIHRoZSB0ZXh0IGFuZCBjaG9vc2UgYSBudW1iZXIgb2YgdG9waWNzLiBUaGF0IHNhaWQsIHRoaXMgYWxzbyBtZWFucyB0aGF0IHRoZSByZXN1bHRzIGNhbiBiZSBzb21ld2hhdCBoYXJkIHRvIGludGVycHJldDogeW91J2xsIGhhdmUgdG8gbWFrZSBhbiBlZHVjYXRlZCBndWVzcyBhYm91dCB3aGF0IHRvcGljcyB5b3UncmUgZmluZGluZyBieSBsb29raW5nIGF0IHdvcmRzIHRoYXQgb2NjdXIgd2l0aCBhIGhpZ2hlciBmcmVxdWVuY3kgaW4gYSBnaXZlbiB0b3BpYy4NCg0KTm90ZTogSWYgeW91J3JlIGxvb2tpbmcgZm9yIGEgcXVpY2sgbW9zdGx5IG5vbi10ZWNobmljYWwgcHJpbWVyIG9uIHRoaXMsIEkgdGhpbmsgRWR3aW4gQ2hlbidzIFtibG9nIHBvc3RdKGh0dHBzOi8vYmxvZy5lY2hlbi5tZS8yMDExLzA4LzIyL2ludHJvZHVjdGlvbi10by1sYXRlbnQtZGlyaWNobGV0LWFsbG9jYXRpb24vKSBpcyBhIGdvb2Qgc3RhcnRpbmcgcG9pbnQsIGJ1dCB0aGVyZSdzIGEgbG90IG9mIGluZm9ybWF0aW9uIGFib3V0IHRoZXNlIG9ubGluZS4gSWYgeW91J3JlIGxvb2tpbmcgZm9yIGEgZGVlcGVyIGRpdmUsIFtoZXJlJ3MgYSBnb29kIHJldmlldyBhcnRpY2xlXShodHRwczovL2xpbmsuc3ByaW5nZXIuY29tL2FydGljbGUvMTAuMTAwNy9zMTEwNDItMDE4LTY4OTQtNCkgb24gdGhlIG1ldGhvZCBhbmQgcmVjZW50IGlubm92YXRpb25zLg0KDQoNCg0KSGVyZSdzIGFuIGV4YW1wbGUgb2YgY3JlYXRpbmcgYW4gTERBIG1vZGVsIHdpdGgganVzdCAxNSB0b3BpY3MuIA0KDQooSWYgeW91IHdhbnQgcmVwbGljYWJsZSByZXN1bHQsIG1ha2Ugc3VyZSB0byBydW4gYHNldC5zZWVkKFtzb21lIG51bWJlcl0pYCByaWdodCBiZWZvcmUgeW91IHRyYWluIHRoZSB0b3BpYyBtb2RlbC4gV2hpbGUgdG9waWMgbW9kZWxzIHdpbGwgdGhlb3JldGljYWxseSBjb252ZXJnZSB0b3dhcmQgYSBzaW5nbGUgImNvcnJlY3QiIGFuc3dlciwgdGhlIG51bWJlcmluZyBvZiB0aGUgdG9waWNzIGlzIGNvbXBsZXRlbHkgYXJiaXRyYXJ5IGluIHN0YW5kYXJkIExEQSwgc28gdG9waWMgMTAgbWlnaHQgYmUgdG9waWMgMiBuZXh0IHRpbWUgeW91IHJ1biBpdC4pDQoNCmBgYHtyIGxkYX0NCmxpYnJhcnkoc2VlZGVkbGRhKSAjIHBhY2thZ2UgZm9yIGJvdGggc2VlZGVkIExEQSBhbmQgcmVndWxhciBMREENCmxpYnJhcnkoTERBdmlzKSAjIGZvciB2aXN1YWxpemF0aW9uIA0KIyBudW1iZXIgb2YgdG9waWNzOiANCks8LTE1DQoNCiMgdGhlIGluaXRpYWxpemF0aW9uIGlzIHJhbmRvbSwgc28gc2V0IHRoZSByYW5kb20gbnVtYmVyIHNlZWQgZm9yIHJlcGxpY2FiaWxpdHkgDQpzZXQuc2VlZCgxMDApDQpsZGFfbW9kZWw8LXRleHRtb2RlbF9sZGEocG9sX2RmbSwgayA9IEssIA0KICAgICAgICAgICAgICBtYXhfaXRlcj0yMDAwLCANCiAgICAgICAgICAgICAgYXV0b19pdGVyID0gVCwNCiAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICMgcmFpc2luZyB0aGlzIHJlc3VsdCBpbiBtb3JlIHVuaWZvcm0gdGhldGENCiAgICAgICAgICAgICAgYWxwaGEgPSAwLjEsDQogICAgICAgICAgICAgICNyYWlzaW5nIHRoaXMgd2lsbCByZXN1bHQgaW4gYSBtb3JlIHVuaWZvcm0gcGhpDQogICAgICAgICAgICAgIGJldGEgPSAwLjAxDQogICAgICAgICAgICAgIA0KICAgICAgICAgICAgICApDQpgYGANCg0KSWYgeW91IHZpZXcgYGxkYV9tb2RlbCRwaGlgIHlvdSBzaG91bGQgc2VlIGEgbWF0cml4IHdpdGggMTUgcm93cyAob25lIHJvdyBwZXIgdG9waWMpIGFuZCBhcm91bmQgODAwMCBjb2x1bW5zIChvbmUgY29sdW1uIHBlciB0ZXJtKS4gVGhlIG51bWJlciBpbiBlYWNoIHJvdyByZXByZXNlbnQgdGhlIHByb2JhYmlsaXR5IG9mIHRoYXQgdGVybSBmb3IgdGhlIHRvcGljLiAoeW91J2xsIHNvbWV0aW1lcyBzZWUgcGVvcGxlIHJlZmVyIHRvIHRoaXMgbWF0cml4IHVzaW5nIHRoZSBHcmVlayBsZXR0ZXIgJFxwaGkkIG9yIGFzIHRoZSAidG9waWMtd29yZCBkaXN0cmlidXRpb24iKQ0KDQpZb3UgY2FuIHNlZSB0aGUgaGlnaGVzdCBwcm9iYWJpbGl0eSB3b3JkcyBpbiBlYWNoIHRvcGljIHVzaW5nIHRoZSBjb21tYW5kIGJlbG93LCB5b3UnbGwgcHJvYmFibHkgYmUgYWJsZSB0byBwaWNrIG91dCBzb21lIGNvbmNlcHRzIHByZXR0eSBxdWlja2x5DQoNCmBgYHtyfQ0KDQojdG9wIDEwIHRlcm1zIHBlciB0b3BpYw0KdGVybXMobGRhX21vZGVsLCAxMCklPiUNCiAgI2NvbnZlcnRpbmcgdG8gZGF0YSBmcmFtZSAoZm9yIHByZXR0aWVyIGZvcm1hdHRpbmcpDQogIGRhdGEuZnJhbWUoKQ0KDQpgYGANCg0KDQpNZWFud2hpbGUsIHRoZSBgbGRhX21vZGVsJHRoZXRhYCByZXByZXNlbnRzIHRoZSB0b3BpYy13b3JkIGRpc3RyaWJ1dGlvbiBtYXRyaXguIElmIHlvdSB2aWV3IHRoaXMgb2JqZWN0IHlvdSBzaG91bGQgc2VlIG9uZSBjb2x1bW4gcGVyIHRvcGljIGFuZCBvbmUgcm93IHBlciBkb2N1bWVudC4gVGhlIG51bWJlcnMgaW5kaWNhdGUgdGhlIHByb2JhYmlsaXR5IG9mIGEgdG9waWMgaW4gYSBnaXZlbiBkb2N1bWVudC4gKHlvdSdsbCBvZnRlbiBzZWUgdGhpcyBkaXN0cmlidXRpb24gZGVub3RlZCBhcyAkXHRoZXRhJCBvciBhcyB0aGUgImRvY3VtZW50LXRvcGljIG1hdHJpeCIpDQoNCmBgYHtyfQ0KIyBnZXQgdGhlIGRvY3VtZW50LXRvcGljIGRpc3RyaWJ1dGlvbnMgZm9yIHRoZSBmaXJzdCBmZXcgYXJ0aWNsZXM6IA0KaGVhZChsZGFfbW9kZWwkdGhldGEpJT4lDQogIGRhdGEuZnJhbWUoKQ0KDQpgYGANCg0KT25lIHVzZSBvZiB0aGUgdGhlIGRvY3VtZW50IHRvcGljIGRpc3RyaWJ1dGlvbiBpcyBmb3IgaW5mb3JtYXRpb24gcmV0cmlldmFsLiBCYXNlZCBvbiB0aGUgbW9zdCBsaWtlbHkgdGVybXMsIHRvcGljIDEzIGFwcGVhcnMgdG8gYmUgcGlja2luZyB1cCBvbiBzdG9yaWVzIHJlbGF0ZWQgdG8gdGhlIFN1cHJlbWUgQ291cnQncyBkZWNpc2lvbiBvdmVydHVybmluZyBSb2Ugdi4gV2FkZS4gU28gZG9jdW1lbnRzIHdpdGggYSBoaWdoICUgb2YgdGhpcyB0b3BpYyBzaG91bGQgcHJvYmFibHkgYmUgYWJvdXQgdGhhdCBzdG9yeS4gQW5kIGJhc2VkIG9uIHRoZSB1cmxzLCB0aGlzIGludHVpdGlvbiBzZWVtcyBjb3JyZWN0OiANCg0KYGBge3J9DQogICAgI2dldCBkb2N1bWVudCBkYXRhDQpkb2Jic19kb2NzPC1kb2N2YXJzKHBvbF9kZm0pJT4lDQogIA0KICAjIGFkZCAlIG9mIHRvcGljIDEzIHRvIGRvY3VtZW50IGRhdGENCiAgbXV0YXRlKGRvYmJzID0gbGRhX21vZGVsJHRoZXRhWyw0XSklPiUNCiAgIyBzb3J0IGZyb20gbG93ZXN0IHRvIGhpZ2hlc3QgdG9waWMgJQ0KICBhcnJhbmdlKC1kb2JicykNCg0KDQpkb2Jic19kb2NzJT4lDQogIHNlbGVjdCh1cmwpJT4lDQogIHNsaWNlX2hlYWQobj0xMCkNCg0KDQoNCmBgYA0KDQoNCldlIGNhbiB1c2UgdGhpcyBpbnNpZ2h0IHRvIGdldCBzb21lIHNlbnNlIG9mIGhvdyB0aGUgdm9sdW1lIG9mIGNvdmVyYWdlIG9uIHRoaXMgdG9waWMgaGFzIGNoYW5nZWQgb3ZlciB0aW1lLiBIZXJlLCB3ZSdyZSBnZXR0aW5nIHRoZSBtb250aGx5IGF2ZXJhZ2UgJSBvZiB0aGlzIHRvcGljIHdpdGhpbiBlYWNoIGRvY3VtZW50LiBUaGUgYGRvYmJzX3RpbWVsaW5lYCBvYmplY3QganVzdCBhbGxvd3MgdXMgdG8gYWRkIGEgc2V0IG9mIGFubm90YXRpb25zIHRvIHRoZSB0aW1lbGluZS4NCg0KYGBge3IgZG9iYnNwcmVwfQ0KIyBtYWtlIGEgdGltZWxpbmUgb2YgZXZlbnRzIChzbyB3ZSBjYW4gYWRkIGFubm90YXRpb25zIHRvIHRoZSB0aW1lbGluZSkNCmRvYmJzX3RpbWVsaW5lPC1kYXRhLmZyYW1lKGV2ZW50PSBjKCdHaW5zYnVyZyBkZWF0aCcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnQ2VydCBncmFudGVkJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdPcmFsIGFyZ3VtZW50cycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRG9iYnMgbGVhaycsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0RvYmJzIG9waW5pb24nKSwNCiAgICAgICAgICAgICAgICBkYXRlID0gYXMuRGF0ZShjKCIyMDIwLTA5LTE4IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcyMDIxLTA1LTE3JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcyMDIxLTEyLTAxJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcyMDIyLTA1LTAyJywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnMjAyMi0wNi0yNCcpKQ0KICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICApJT4lDQogIA0KICAjIGNvbnZlcnQgdGltZWxpbmUgdG8gbW9udGhzICh0byBhbGxvdyBqb2luaW5nIHdpdGggbW9udGhseSBjb3ZlcmFnZSBkYXRhKQ0KICBtdXRhdGUobW9udGggPSBmbG9vcl9kYXRlKGRhdGUsIHVuaXQ9J21vbnRocycpKQ0KDQptb250aGx5PC1kb2Jic19kb2NzJT4lDQogICMgcm91bmQgZG93biB0byB0aGUgbmVhcmVzdCBtb250aA0KICBtdXRhdGUobW9udGggPSBmbG9vcl9kYXRlKGRhdGUsIHVuaXQ9J21vbnRocycpDQogICAgICAgICApJT4lDQogIA0KICAjIGdyb3VwIGJ5IG1vbnRoIGFuZCBjb21wdXRlIHRoZSBtb250aGx5IGF2ZXJhZ2U6IA0KICBncm91cF9ieShtb250aCklPiUNCiAgc3VtbWFyaXNlKGNvdmVyYWdlID0gbWVhbihkb2JicykNCiAgICAgICAgICAgICklPiUNCiAgIyBqb2luIHdpdGggdGltZWxpbmUgZGF0YQ0KICBsZWZ0X2pvaW4oZG9iYnNfdGltZWxpbmUpDQpgYGANCg0KQW5kIGZyb20gaGVyZSwgd2UnbGwgY3JlYXRlIHRoZSBwbG90IGluIEdHcGxvdDogDQoNCmBgYHtyIGRvYmJzcGxvdH0NCiMgY3JlYXRlIHBsb3Q6IA0KbW9udGhseSU+JQ0KICBnZ3Bsb3QoYWVzKHg9bW9udGgsIHk9Y292ZXJhZ2UsIGxhYmVsPWV2ZW50KSkgKyANCiAgIyBjcmVhdGUgdGhlIGxpbmUgcGxvdA0KICBnZW9tX2xpbmUoKSArDQogICMgYWRkIHBvaW50cyBmb3IgZWFjaCBtb250aA0KICBnZW9tX3BvaW50KCkgKw0KICAjIHR1cm4gb2ZmIGNsaXBwaW5nIChhdm9pZHMgcmVtb3ZpbmcgbGFiZWxzIGlmIHRoZXkncmUgb3V0c2lkZSB0aGUgcGxvdCBhcmVhKQ0KICBjb29yZF9jYXJ0ZXNpYW4oY2xpcCA9ICJvZmYiKSArIA0KICANCiAgIyBhZGQgbGFiZWxzICh2anVzdCAtMSBzZXRzIGFubm90YXRpb24ganVzdCBhYm92ZSB0aGUgdGltZWxpbmUpDQogIGdlb21fbGFiZWwodmp1c3Q9LTEpICsNCiAgeWxhYignQXZlcmFnZSB0b3BpYyAlJykgKw0KICAjIHVzaW5nIHRoZSBtaW5pbWFsIHRoZW1lOiANCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgZ2d0aXRsZSgnQXZlcmFnZSBwZXJjZW50IHRvcGljIDEzIChSb2Ugb3ZlcnR1cm5lZCknKSAgKw0KICAjIHNob3cgdGlja3MgZXZlcnkgMyBtb250aHMNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcz0iMyBtb250aCIsIGRhdGVfbGFiZWxzID0gIiVPYiAnJXkiKSArDQogICMgbGFiZWxzIGF0IDQ1IGRlZ3JlZSBhbmdsZQ0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1KSkNCmBgYA0KDQoNCkxhc3RseSwgeW91IGNhbiB1c2UgdGhlIFtMREF2aXMgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL2Nwc2lldmVydC9MREF2aXMpIHRvIHZpc3VhbGl6ZSBhbmQgaW50ZXJwcmV0IHlvdXIgdG9waWNzLiANCg0KVGhlIGxlZnQgcGFuZWwgdXNlcyBhIFtwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9QcmluY2lwYWxfY29tcG9uZW50X2FuYWx5c2lzKSB0byBtb2RlbCB0aGUgImRpc3RhbmNlIiBiZXR3ZWVuIGVhY2ggdG9waWMuIFRvcGljcyB0aGF0IGFyZSBmdXJ0aGVyIGFwYXJ0IGFyZSBsZXNzIHNpbWlsYXIgdG8gb25lIGFub3RoZXIsIGFuZCBsYXJnZXIgYnViYmxlcyBpbmRpY2F0ZSB0aGUgdG9waWMgaXMgbW9yZSBjb21tb24uIFNlbGVjdGluZyBhIHRvcGljIHdpbGwgY2F1c2UgdGhlIHJpZ2h0IHBhbmVsIHRvIGRpc3BsYXkgcmVsZXZhbnQgdGVybXMgZm9yIHRoYXQgdG9waWMuIE5vdGU6IGNsaWNraW5nIHRoZSBsaW5rcyBpbiB0aGUgYm90dG9tIHJpZ2h0IHdpbGwgYnJpbmcgdXAgbW9yZSBkZXRhaWxlZCBpbmZvcm1hdGlvbiBvbiB3aGF0IHRoZXNlIG1ldHJpY3MgbWVhbiwgYnV0IHRoZSBzaG9ydCB2ZXJzaW9uIGlzIHRoYXQgdGhleSdyZSBib3RoIG1lYW50IHRvIG1ha2UgaXQgZWFzaWVyIHRvIGlkZW50aWZ5IHRoZSBtb3N0IGRpc3RpbmN0aXZlIHRlcm1zIGZvciBlYWNoIHRvcGljLCByYXRoZXIgdGhhbiB0aGUgdGVybXMgdGhhdCBoYXZlIHRoZSBoaWdoZXN0IGZyZXF1ZW5jeS4gDQoNCkluIG9yZGVyIHRvIChob3BlZnVsbHkpIHNpbXBsaWZ5IHRoZSBwcm9jZXNzIG9mIHNldHVwLCB0aGVyZSdzIGEgY3VzdG9tIGZ1bmN0aW9uIGluY2x1ZGVkIGluIHRoZSBoZWFkZXIgb2YgdGhpcyBub3RlYm9vayB0aGF0IHdpbGwgYWxsb3cgeW91IHRvIHNhdmUgYW5kIGVtYmVkIHRoZXNlIHZpc3VhbGl6YXRpb25zLiBIZXJlJ3MgdGhlIGNvZGUgeW91IHdvdWxkIHVzZSB0byBtYWtlIHRoaXMgZGlzcGxheSBvbiB5b3VyIGxvY2FsIGRldmljZToNCg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMgYSBmaWxlbmFtZSB0byBzYXZlIHRoZSBwbG90IHVuZGVyDQptb2RlbHZpczwtJ2xkYV92aXMuaHRtbCcNCiMgY3JlYXRlIHRoZSB2aXN1YWxpemF0aW9uDQpMREF2aXNfc3RhbmRhbG9uZShsZGFfbW9kZWwsIG91dHB1dGZpbGU9bW9kZWx2aXMpDQojIGJyaW5nIHVwIGEgd2luZG93IHdpdGggdGhlIHZpc3VhbGl6YXRpb246IA0KYnJvd3NlVVJMKG1vZGVsdmlzKQ0KDQpgYGANCg0KWW91IGNvdWxkIHRha2Ugc2NyZWVuc2hvdHMgb2YgdGhpcyB2aXN1YWxpemF0aW9uIGZvciBkaWZmZXJlbnQgdG9waWNzIGluIG9yZGVyIHRvIHNoYXJlIGl0LCBidXQgaWYgeW91IHdhbnRlZCB0byBzaGFyZSB0aGUgYWN0dWFsIGludGVyYWN0aXZlIHZlcnNpb24gb2YgdGhpcyB2aXN1YWxpemF0aW9uIGluIGFuIFJtYXJrZG93biBmaWxlIG9yIG5vdGVib29rLCB5b3UgY291bGQgZG8gaXQgdXNpbmcgdGhlIGBpbmNsdWRlSFRNTGAgZnVuY3Rpb24gZnJvbSB0aGUgaHRtbHRvb2xzIHBhY2thZ2UuIA0KDQpgYGB7ciBsZGF2aXMsIHJlc3VsdHM9J2FzaXMnfQ0KbGRhX3ZpczwtJ2xkYV92aXN1YWwuaHRtbCcNCkxEQXZpc19zdGFuZGFsb25lKGxkYV9tb2RlbCwgb3V0cHV0ZmlsZT1sZGFfdmlzKQ0KaHRtbHRvb2xzOjppbmNsdWRlSFRNTChsZGFfdmlzKQ0KYGBgDQoNCiMjIFNlZWRlZCBMREEgDQoNCkluIHNlZWRlZCBMREEsIHlvdSBzdXBwbHkgc29tZSBpbml0aWFsIHRlcm1zIGFuZCB0b3BpY3MsIGFuZCB0aGUgbW9kZWwgd2lsbCBpbmZlciBhZGRpdGlvbmFsIHRlcm1zIGJhc2VkIG9uIHlvdXIgd29yZHMuIA0KDQpIZXJlIEkndmUgY3JlYXRlZCBhIGN1c3RvbSBkaWN0aW9uYXJ5IHdpdGggMTAgdG9waWNzIGFuZCBqdXN0IGEgaGFuZGZ1bCBvZiB0ZXJtcyBmb3IgZWFjaC4gTm90aWNlIHRoYXQgdGVybXMgd2l0aCBgKmAgd2lsbCBiZSBhYmxlIHRvIG1hdGNoIGRpZmZlcmVudCBlbmRpbmdzLCBzbyBgdmFjY2luKmAgc2hvdWxkIGJlIGFibGUgdG8gbWF0Y2ggYW55IG9mIHZhY2NpbmF0ZSwgdmFjY2luZSwgYW5kIHZhY2NpbmF0aW9uLiANCg0KYGBge3Igc2VlZGVkX2xkYX0NCm5ld3NfZGljdDwtbGlzdCgnY292aWQnID0gYygnY292aWQqJywgJ3ZhY2NpbionLCAnbG9ja2Rvd24qJyksDQogICAgICAgICAgICAgICAgJ25hdHNlYycgPSBjKCdhZmdoYW4qJywgJ3RhbGliYW4nLCAncHV0aW4nLCAnY2hpbmEnLCAndWtyYWluKicsJ3plbGVuc2t5JywgJ3RlcnJvcionKSwNCg0KICAgICAgICAgICAgICAgICdlbGVjdGlvbnMnID0gYygnZWxlY3Rpb24nLCAnY2F1Y3VzJywgJ3ByaW1hcnknLCAnZGViYXRlKicsICdub21pbmF0aW9uJywgJ21pZHRlcm0nLCAndm90ZSonLCAncG9sbConKSwNCiAgICAgICAgICAgICAgICAndHJ1bXAnID0gYygnaW5kaWN0bWVudCcsICdpbXBlYWNobWVudCcsICdtYXItYS1sYWdvJyksDQogICAgICAgICAgICAgICAgJ2phbjYnID0gYygiY2FwaXRvbCIsICdqYW51YXJ5JywgJ2NoYW5zbGV5JywnZnJhdWQnKSwNCiAgICAgICAgICAgICAgICAnYmxtJyAgPSBjKCdmbG95ZCcgLCAnYmxhY2snLCAncG9saWNlJywgInByb3Rlc3QqIiksDQogICAgICAgICAgICAgICAgJ2ltbWlncmF0aW9uJyA9IGMoJ2ltbWlncmF0KicsICAnYm9yZGVyJywgJ2hvbWVsYW5kJywgJ21pZ3JhbnQqJyksDQogICAgICAgICAgICAgICAgJ2NsaW1hdGUnID0gYygnZ2xvYmFsJywnY2xpbWF0ZSonLCdmdWVsJywgJ2NhcmJvbicsICdlbWlzc2lvbionKSwNCiAgICAgICAgICAgICAgICAnYWJvcnRpb24nID0gYygnYWJvcnQqJywgICdyb2UnLCAnd2FkZScsICdmZXR1cycsICdkb2JicycpLA0KICAgICAgICAgICAgICAgICdlY29ub215JyA9IGMoJ2luZmxhdGlvbicsJ3ByaWNlKicsICd0YXgnKQ0KICAgICAgICAgICAgICAgICkNCiMgY29udmVyIHRvIGEgcXVhbnRlZGEgZGljdGlvbmFyeQ0KZGljdF90b3BpYzwtZGljdGlvbmFyeShuZXdzX2RpY3QpDQpzZXQuc2VlZCgxMDApICMgc2V0dGluZyByYW5kb20gbnVtYmVyIHNlZWQNCnNsZGFfbW9kZWwgPC0gdGV4dG1vZGVsX3NlZWRlZGxkYShwb2xfZGZtLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpY3Rpb25hcnkgPSBkaWN0X3RvcGljLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF1dG9faXRlciA9IFQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzaWR1YWwgPSA1LCAjIDUgcmVzaWR1YWwgdG9waWNzIGZvciBnYXJiYWdlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfaXRlcj0yMDAwKQ0KYGBgDQoNCkFnYWluLCB3ZSBjYW4gZXh0cmFjdCB0b3BpY3MgZnJvbSB0aGlzIG1vZGVsIHVzaW5nIHRoZSBgdGVybXNgIGZ1bmN0aW9uLiBZb3UnbGwgbm90aWNlIHRoYXQgdGhlcmUgYXJlIHRvcGljcyB0aGF0IGNvcnJlc3BvbmQgdG8gdGhlIGNhdGVnb3JpZXMgaW4gdGhlIGRpY3Rpb25hcnksIGJ1dCB0aGVyZSBhcmUgbG90cyBvZiB3b3JkcyB0aGF0IHdlcmVuJ3QgaW5pdGlhbGx5IGdpdmVuIHRvIHRoZSBtb2RlbC4gT25lIHVzZWZ1bCBhcHBsaWNhdGlvbiBvZiB0aGlzIGFwcHJvYWNoIGlzIGl0IGNhbiBoZWxwIHlvdSBjb21lIHVwIGFkZGl0aW9uYWwgdGVybXMgd2hlbiBjb25zdHJ1Y3RpbmcgYSBkaWN0aW9uYXJ5LiANCg0KDQpgYGB7ciBzbGRhdGVybXN9DQp0ZXJtcyhzbGRhX21vZGVsKSU+JQ0KICAjY29udmVydGluZyB0byBhIGRhdGEgZnJhbWUgKGp1c3QgdG8gbWFrZSBpdCBsb29rIG5pY2VyIGluIHRoZSBub3RlYm9vaykNCiAgZGF0YS5mcmFtZSgpDQpgYGANCg0KDQpBbmQsIGp1c3QgbGlrZSB3aXRoIHJlZ3VsYXIgTERBLCB3ZSBjYW4gdXNlIHRoZSBpbmZlcnJlZCB0b3BpY3MgdG8gZG8gdGhpbmdzIGxpa2UgdHJhY2sgbmV3cyBjb3ZlcmFnZSBvdmVyIHRpbWUgKG5vdGUgdGhhdCB0aGUgY29sdW1ucyBub3cgaGF2ZSBuYW1lcyBiYXNlZCBvbiBvdXIgZGljdGlvbmFyeSkgSGVyZSdzIGhvdyBjb3ZlcmFnZSBvZiBDb3ZpZC0xOSB0b3BpYyBzdGVhZGlseSBkcm9wcGVkIHNpbmNlIDIwMjAuIA0KDQoNCmBgYHtyIGNvdmlkcGxvdH0NCg0KY292aWRfZGY8LQ0KICBkb2N2YXJzKHBvbF9kZm0pJT4lDQogICMgYWRkICUgb2YgY292aWQgdG9waWMNCiAgbXV0YXRlKGNvdmlkID0gc2xkYV9tb2RlbCR0aGV0YVssJ2NvdmlkJ10sDQogICAgICAgICAgICMgcm91bmQgZG93biB0byB0aGUgbmVhcmVzdCBtb250aA0KICAgICAgICAgbW9udGggPSBmbG9vcl9kYXRlKGRhdGUsIHVuaXQ9J21vbnRocycpKSU+JQ0KICAjIGdyb3VwIGJ5IG1vbnRoIGFuZCBjb21wdXRlIHRoZSBtb250aGx5IGF2ZXJhZ2U6IA0KICBncm91cF9ieShtb250aCklPiUNCiAgc3VtbWFyaXNlKGNvdmlkID0gbWVhbihjb3ZpZCkpDQpjb3ZpZF9kZiU+JQ0KICBnZ3Bsb3QoYWVzKHg9bW9udGgsIHk9Y292aWQpKSArIA0KICAjIGNyZWF0ZSB0aGUgbGluZSBwbG90DQogIGdlb21fbGluZSgpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgI2FkZCBhIG1hcmtlciBmb3IgdGhlIHN0YXJ0IG9mIEZsb3lkIHByb3Rlc3RzOg0KDQogIHlsYWIoJ0F2ZXJhZ2UgdG9waWMgJScpICsNCiAgIyB1c2luZyB0aGUgbWluaW1hbCB0aGVtZTogDQogIHRoZW1lX21pbmltYWwoKSArDQogIGdndGl0bGUoJ0F2ZXJhZ2UgJSBjb3ZpZCB0b3BpYyBieSBtb250aCcpICsNCiAgc2NhbGVfeF9kYXRlKGJyZWFrcz0iMyBtb250aCIsIGRhdGVfbGFiZWxzID0gIiVPYiAnJXkiKSArDQogICMgbGFiZWxzIGF0IDQ1IGRlZ3JlZSBhbmdsZQ0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1KSkNCiAgDQpgYGANCg0KDQpMYXN0bHksIHRoZSBzYW1lIG1ldGhvZCB3ZSB1c2VkIGZvciBMREFWaXMgYWJvdmUgc2hvdWxkIHdvcmsgaGVyZSAodW5mb3J0dW5hdGVseSB0aGlzIGRvZXNuJ3QgcGljayB1cCBvbiB0aGUgdG9waWMgbmFtZXMpDQoNCg0KYGBge3Igc2xkYXZpcywgcmVzdWx0PSdhc2lzJ30NCnNsZGFfdmlzPC0nc2xkYV92aXMuaHRtbCcNCkxEQXZpc19zdGFuZGFsb25lKHNsZGFfbW9kZWwsIG91dHB1dGZpbGU9c2xkYV92aXMpDQpodG1sdG9vbHM6OmluY2x1ZGVIVE1MKHNsZGFfdmlzKQ0KYGBgDQoNCiMjIyBTdHJ1Y3R1cmFsIFRvcGljIE1vZGVscw0KDQpVbmxpa2UgdHJhZGl0aW9uYWwgTERBLCBTdHJ1Y3R1cmFsIFRvcGljIE1vZGVscyBbKHBhcGVyKV0oaHR0cHM6Ly9zY2hvbGFyLmhhcnZhcmQuZWR1L2ZpbGVzL2R0aW5nbGV5L2ZpbGVzL3RvcGljbW9kZWxzb3BlbmVuZGVkZXhwZXJpbWVudHMucGRmKSBhbGxvdyB1c2VycyB0byBpbmNsdWRlIGFkZGl0aW9uYWwgZG9jdW1lbnQtbGV2ZWwgZGF0YSB3aGVuIGluZmVycmluZyB0aGVpciB0b3BpY3MuIFRoZSAicHJldmFsZW5jZSBtb2RlbCIgd2lsbCBhbGxvdyB0b3BpYyBwcm9iYWJpbGl0aWVzIHRvIHZhcnkgc3lzdGVtYXRpY2FsbHkgYWNjb3JkaW5nIHRvIGEgY2hhcmFjdGVyaXN0aWMgb2YgdGhlIGRvY3VtZW50IChzdWNoIGFzIHRoZSBhdXRob3Igb3Igc291cmNlKSwgdGhlICJjb250ZW50IG1vZGVsIiB3aWxsIGFsbG93IHRoZSB0ZXJtLXByb2JhYmlsaXRpZXMgd2l0aGluIGVhY2ggdG9waWMgdG8gdmFyeSBpbiB0aGUgc2FtZSB3YXkuIEZvciBpbnN0YW5jZTogaWYgd2UgdGhpbmsgdGhhdCBkaWZmZXJlbnQgc291cmNlcyBhcmUgbW9yZSBsaWtlbHkgdG8gdGFsayBhYm91dCBjZXJ0YWluIGlzc3Vlcywgd2UgY2FuIGV4cGxpY2l0bHkgbW9kZWwgdGhhdCBpbmZvcm1hdGlvbiBhcyBwYXJ0IG9mIHRoZSBpbmZlcmVuY2UgcHJvY2Vzcy4gSGVyZSwgd2UncmUgdXNpbmcgdGhlIHNvdXJjZSB2YXJpYWJsZSAoRm94IE5ld3MgdnMuIENOTikgYXMgYSBwcmVkaWN0b3IgZm9yIHRoZSB0b3BpYyBwcm9iYWJpbGl0aWVzLiBUaGUgYXNzdW1wdGlvbiAoYSByZWFzb25hYmx5IHNhZmUgb25lKSBpcyB0aGF0IHRoZSB0d28gb3V0bGV0cyB3aWxsIGRpZmZlciBzeXN0ZW1hdGljYWxseSBpbiB0aGUgc29ydHMgb2YgbmV3cyB0aGV5IGNvdmVyLiANCg0KYGBge3J9DQpsaWJyYXJ5KHN0bSkgIyBmb3Igc3RydWN0dXJhbCB0b3BpYyBtb2RlbHMNCiMgY29udmVydCBERk0gdG8gYSBmb3JtYXQgdXNhYmxlIGJ5IFNUTQ0KZGZtMnN0bSA8LSBjb252ZXJ0KHBvbF9kZm0sIHRvID0gInN0bSIpDQojIG51bWJlciBvZiB0b3BpY3MNCks8LTE1DQojIHJhbmRvbSBudW1iZXIgc2VlZCANCnNldC5zZWVkKDEwMDApDQojIHJ1biB0aGUgbW9kZWwNCnN0bV9tb2RlbCA8LSBzdG0oZGZtMnN0bSRkb2N1bWVudHMsIGRmbTJzdG0kdm9jYWIsIA0KICAgICAgICAgICAgICAgICBLID0gSywgDQogICAgICAgICAgICAgICAgIGRhdGEgPSBkZm0yc3RtJG1ldGEsIA0KICAgICAgICAgICAgICAgICAjIGluY2x1ZGUgc291cmNlIGluIHRoZSBwcmV2YWxlbmNlIG1vZGVsDQogICAgICAgICAgICAgICAgIHByZXZhbGVuY2UgPSAgfiBzb3VyY2UsDQogICAgICAgICAgICAgICAgIGluaXQudHlwZSA9ICJTcGVjdHJhbCIsDQogICAgICAgICAgICAgICAgICMgc2V0IHZlcmJvc2UgPVQgaWYgeW91IHdhbnQgdG8gd2F0Y2ggdGhlIG1vZGVsIHByb2dyZXNzDQogICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGIA0KICAgICAgICAgICAgICAgICApDQoNCg0KYGBgDQoNCg0KVGhlIFNUTSBwYWNrYWdlIGluY2x1ZGVzIGEgc3BlY2lhbCBgbGFiZWxUb3BpY3NgIGZ1bmN0aW9uIHRvIHJldHJpZXZlIHRoZSB0ZXJtcyBhc3NvY2lhdGVkIHdpdGggZWFjaCB0b3BpYy4gV2hpbGUgdGhlIGhpZ2hlc3QgcHJvYmFiaWxpdHkgdGVybXMgY2FuIGJlIGEgZ29vZCB3YXkgdG8gaWRlbnRpZnkgdG9waWNzLCBpdCBjYW4gYmUgc29tZXdoYXQgdW5oZWxwZnVsIGJlY2F1c2Ugc29tZXRpbWVzIHRlcm1zIHNob3cgdXAgYXQgdGhlIHRvcGljIG9mIHRoZSBsaXN0IGJlY2F1c2UgdGhleSdyZSBqdXN0IGNvbW1vbiB3b3JkcyBpbiBnZW5lcmFsLiBUaGUgRlJFWCwgbGlmdCwgYW5kIHNjb3JlIG1ldHJpY3MgYWxsIGF0dGVtcHQgdG8gYWNjb3VudCBmb3IgdGVybSBmcmVxdWVuY3kgaW4gc29tZSB3YXkgaW4gb3JkZXIgdG8gZ2l2ZSBncmVhdGVyIHdlaWdodCB0byB0ZXJtcyB0aGF0IGFyZSBtb3JlIGRpc3RpbmN0IHRvIGVhY2ggdG9waWMuIA0KDQpgYGB7cn0NCg0KbGFiZWxUb3BpY3Moc3RtX21vZGVsLCB0b3BpY3M9MTo1LCBuPTUpICMgbm90ZSAtIG9ubHkgc2hvd2luZyBmaXJzdCA1IG9mIDE1IHRvcGljcyBoZXJlDQoNCmBgYA0KDQpZb3UgY2FuIHZpZXcgYHN0bV9tb2RlbCR0aGV0YWAgdG8gZ2V0IHRoZSBkb2N1bWVudC10b3BpYyBwcm9iYWJpbGl0aWVzLiBCdXQgU1RNIGFsc28gaW5jbHVkZXMgdGhlIGBmaW5kVGhvdWdodHNgIGNvbW1hbmQgdG8gaGVscCBpZGVudGlmeSBkb2N1bWVudHMgdGhhdCBhcmUgcmVwcmVzZW50YXRpdmUgb2YgYSBnaXZlbiB0b3BpYy4gSGVyZSwgSSdtIGdldHRpbmcgYSBkb2N1bWVudCB0aGF0IGlzIHJlcHJlc2VudGF0aXZlIG9mIHRvcGljIDYsIHdoaWNoIGFwcGVhcnMgdG8gYmUgcmVsYXRlZCB0byBpbW1pZ3JhdGlvbiBhbmQgYm9yZGVyIGNvbnRyb2wgaXNzdWVzIA0KYGBge3IsUi5vcHRpb25zPWxpc3QobWF4LnByaW50PTEwKX0NCmZpbmRUaG91Z2h0cyhzdG1fbW9kZWwsIA0KICAgICAgICAgICAgICMgbGlzdCBvZiB0ZXh0cyAodGhlIGRpbW5hbWVzIHBhcnQgaGVyZSBlbnN1cmVzIHRoYXQgdGhlIGRvYyBuYW1lcyBsaW5lIHVwIHdpdGggdGhlIG5hbWVzIGluIHRoZSBERk0pDQogICAgICAgICAgICAgdGV4dHMgPSBwb2xfY29ycHVzW3BvbF9kZm1ARGltbmFtZXNbWzFdXV0sIA0KICAgICAgICAgICAgICMgdG9waWMgNg0KICAgICAgICAgICAgIHRvcGljcyA9IDYgLA0KICAgICAgICAgICAgIG4gPSAxKQ0KYGBgDQoNCg0KVGhlIG1vcmUgaW1wb3J0YW50IGlubm92YXRpb24gaGVyZSBpcyB0aGF0IHdlIGNhbiBlc3RpbWF0ZSB0aGUgZWZmZWN0IG9mIGEgY292YXJpYXRlIG9uIHRoZSB0b3BpYyBwcmV2YWxlbmNlLiBIZXJlLCBJJ20gZXN0aW1hdGluZyB0aGUgZWZmZWN0IG9mIHNvdXJjZSBvbiB0aGUgcHJldmFsZW5jZSBvZiBhbGwgMTUgdG9waWNzIChhbHRob3VnaCBJJ20gb25seSBzaG93aW5nIHRoZSByZXN1bHRzIGZvciB0b3BpYyA2KQ0KDQpgYGB7cn0NCmZ4IDwtIGVzdGltYXRlRWZmZWN0KDE6MTUgfiBzb3VyY2UgLCBzdG1fbW9kZWwsDQogICAgICAgICAgICAgICAgICAgICAgICBtZXRhID0gZGZtMnN0bSRtZXRhKQ0KIyBvbmx5IHNob3dpbmcgdG9waWMgNiAocmVtb3ZlIHRoZSAkdGFibGVzW1s2XV0gcGFydCB0byBzZWUgYWxsIHRvcGljcykNCnN1bW1hcnkoZngpJHRhYmxlc1tbNl1dDQoNCmBgYA0KDQpXZSBjYW4gYWxzbyBwbG90IHRoZSByZXN1bHRzIChhbHRob3VnaCB0aGlzIGlzIHZlcnkgbWVzc3kgZm9yIG1vcmUgdGhhbiBhIGhhbmRmdWwgb2YgdG9waWNzKS4gSW4gZWl0aGVyIGNhc2UsIHdlIHNlZSBGb3ggaXMgZ2VuZXJhbGx5IG1vcmUgbGlrZWx5IHRvIGNvdmVyIHRoZSBpbW1pZ3JhdGlvbiB0b3BpYyBjb21wYXJlZCB0byBDTk4uDQoNCmBgYHtyfQ0KcGxvdC5lc3RpbWF0ZUVmZmVjdChmeCwgDQogICAgICAgICAgICAgICAgICAgIGNvdmFyaWF0ZSA9ICJzb3VyY2UiLCANCiAgICAgICAgICAgICAgICAgICAgdG9waWNzID0gMTo2LA0KICAgICAgICAgICAgICAgICAgICAjIHZhbHVlIG9mIGZveCBuZXdzIC0gY25uDQogICAgICAgICAgICAgICAgICAgIGNvdi52YWx1ZTEgPSAiRm94IE5ld3MiLA0KICAgICAgICAgICAgICAgICAgICBjb3YudmFsdWUyICA9ICJDTk4iLA0KICAgICAgICAgICAgICAgICAgICBtZXRob2Q9J2RpZmZlcmVuY2UnLA0KICAgICAgICAgICAgICAgICAgICAjIHlvdSdsbCBuZWVkIHRvIGFkanVzdCB0aGlzIHRvIGtlZXAgdGhlIHRleHQgZnJvbSBnZXR0aW5nIGN1dCBvZmYgDQogICAgICAgICAgICAgICAgICB4bGltICA9IGMoLS4xLCAuMSkNCiAgICAgICAgICAgICAgICAgICAgKQ0KYGBgDQoNClRoZXJlJ3MgYWxzbyBhIChzbGlnaHRseSBqYW5reSkgcGFja2FnZSBjYWxsZWQgW3N0bWluc2lnaHRzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvc3RtaW5zaWdodHMvdmlnbmV0dGVzL2ludHJvLmh0bWwpIHRoYXQgd2lsbCBhbGxvdyB5b3UgdG8gZG8gc29tZSBvZiB0aGlzIGludGVyYWN0aXZlbHkuIFRoaXMgaXNuJ3Qgc29tZXRoaW5nIHlvdSBjYW4gcmVhbGx5IHNoYXJlIHVubGVzcyB5b3UgdXBsb2FkIGl0IHRvIGEgc2VydmVyIHNvbWV3aGVyZSwgYnV0IHlvdSBjYW4gdXNlIGl0IHRvIGdlbmVyYXRlIGFuZCB0aGVuIGRvd25sb2FkIHNvbWUgcGxvdHMgd2hpY2ggeW91IGNvdWxkIHVzZSBlbHNld2hlcmUuIA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMgZm9yIHNvbWUgcmVhc29uLCB0aGUgZGZtIGRhdGEgbXVzdCBiZSBuYW1lZCAib3V0IiBvciBlbHNlIHRoaXMgZG9lc24ndCB3b3JrLiANCm91dDwtZGZtMnN0bQ0KIyBzYXZlIHRoZSByZWxldmFudCBtb2RlbCBvdXRwdXRzOiANCnNhdmUoZmlsZT0nc3RtX21vZGVsLlJEYXRhJywgbGlzdCA9IGMoJ3N0bV9tb2RlbCcsICdvdXQnLCAnZngnLCAncG9sX2NvcnB1cycpKQ0KbGlicmFyeShzdG1pbnNpZ2h0cykNCiMgZ28gdG8gYnJvd3NlIGFuZCBmaW5kIHN0bV9tb2RlbC5SZGF0YSB0byBnZXQgdGhpcyBydW5uaW5nIA0KcnVuX3N0bWluc2lnaHRzKCkNCmBgYA0KDQoNCiMjIEtleXdvcmQgQXNzaXN0ZWQgVG9waWMgTW9kZWxzDQoNCktleXdvcmQgQXNzaXN0ZWQgVG9waWMgbW9kZWxzIFsoc2VlOiBwYXBlcildKGh0dHBzOi8vaW1haS5mYXMuaGFydmFyZC5lZHUvcmVzZWFyY2gvZmlsZXMva2V5QVRNLnBkZikgYXJlIGEgbmV3IGFwcHJvYWNoIHRoYXQgYWxsb3dzIGJvdGggZG9jdW1lbnQgY292YXJpYXRlcyBhbmQgc2VlZCB0ZXJtcyB0byBkZWZpbmUgdG9waWNzLiBJbiBzaG9ydDogaXQgY29tYmluZXMgdGhlIFNlZWRlZExEQSBhcHByb2FjaCB3aXRoIHRoZSBTdHJ1Y3R1cmFsIFRvcGljIE1vZGVsLiBUaGlzIGlzIGEgcHJldHR5IG5ldyBtZXRob2QgYW5kIHRoZSBwYWNrYWdlIGlzIHN0aWxsIHVuZGVyIGRldmVsb3BtZW50LCBidXQgSSdtIGluY2x1ZGluZyBpdCBoZXJlIGJlY2F1c2UgaXQgbWlnaHQgYmUgb2YgaW50ZXJlc3QgZm9yIHRob3NlIG9mIHlvdSB3aG8gYXJlIGFscmVhZHkgdXNpbmcgZGljdGlvbmFyeSBiYXNlZCBtZXRob2RzLiANCg0KVGhlIHNldHVwIGZvciB0aGUgYmFzaWMgbW9kZWwgaXMgYWxtb3N0IGlkZW50aWNhbCB0byBzZWVkZWQgTERBLCB0aGUgbWFpbiBwcmFjdGljYWwgZGlmZmVyZW5jZSBpcyB0aGF0IGl0IGF1dG9tYXRpY2FsbHkgaW5mZXJzIHRoZSAkXGFscGhhJCBhbmQgJFxiZXRhJCBwYXJhbWV0ZXJzIHRoYXQgY29udHJvbCBob3cgZGlmZnVzZSB0aGUgJFx0aGV0YSQgYW5kICRccGhpJCBhcmUsIHdoaWNoIGNhbiBzYXZlIHlvdSB0aGUgZWZmb3J0IG9mIGhhdmluZyB0byB0dW5lIHRoZXNlIHBhcmFtZXRlcnMuIA0KDQpXZSBjYW4gdXNlIHRoZSBzYW1lIGRpY3Rpb25hcnkgd2UgaW1wbGVtZW50ZWQgYmVmb3JlLiBUaGUga2V5QVRNIHBhY2thZ2UgYWxzbyBwcm92aWRlcyBzb21lIGFkZGl0aW9uYWwgb3B0aW9ucyBmb3IgY2hlY2tpbmcgdGhlIHF1YWxpdHkgb2Ygb3VyIHNlbGVjdGVkIGtleXdvcmQgLSBub3RpY2UgdGhhdCBpdCB0aHJvd3MgYSB3YXJuaW5nIGJlY2F1c2Ugc29tZSBvZiB0aGUgdGVybXMgaW4gb3VyIGtleXdvcmQgbGlzdCBkb24ndCBhY3R1YWxseSBhcHBlYXIgaW4gdGhlIGNvcnB1cy4gU29tZSB0ZXJtcyB3ZXJlIHByb2JhYmx5IHRvbyBjb21tb24gYW5kIGdvdCByZW1vdmVkIHdoZW4gd2UgcmFuIGBkZm1fdHJpbWAsIG90aGVyIHRlcm1zIG1heSBqdXN0IGJlIHRvbyByYXJlIHRvIGJlIHVzZWZ1bC4gDQoNCkFsdGhvdWdoIGl0IHdpbGwgbWFrZSB0aGUgbW9kZWwgcnVuIHNsb3dlciwgeW91IG1pZ2h0IHdhbnQgdG8gaW5jcmVhc2UgIGBtYXhfZG9jZnJlcWAgd2hlbiB5b3UgY2xlYW4gdGhlIGRhdGEuIA0KDQpgYGB7cn0NCmxpYnJhcnkoa2V5QVRNKSAjIGtleXdvcmQgYXNzaXN0ZWQgdG9waWMgbW9kZWxzDQojIG91ciBuZXdzIGRpY3Rpb25hcnkNCm5ld3NfZGljdDwtbGlzdCgnY292aWQnID0gYygnY292aWQqJywgJ3ZhY2NpbionLCAnbG9ja2Rvd24qJyksDQogICAgICAgICAgICAgICAgJ25hdHNlYycgPSBjKCdhZmdoYW4qJywgJ3RhbGliYW4nLCAncHV0aW4nLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2NoaW5hJywgJ3VrcmFpbionLCd6ZWxlbnNreScsICd0ZXJyb3IqJyksDQogICAgICAgICAgICAgICAgJ2VsZWN0aW9ucycgPSBjKCdlbGVjdGlvbicsICdjYXVjdXMnLCAncHJpbWFyeScsICdkZWJhdGUqJywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdub21pbmF0aW9uJywgJ21pZHRlcm0nLCAndm90ZSonLCAncG9sbConKSwNCiAgICAgICAgICAgICAgICAndHJ1bXAnID0gYygnaW5kaWN0bWVudCcsICdpbXBlYWNobWVudCcsICdtYXItYS1sYWdvJyksDQogICAgICAgICAgICAgICAgJ2phbjYnID0gYygiY2FwaXRvbCIsICdqYW51YXJ5JywgJ2NoYW5zbGV5JywnZnJhdWQnKSwNCiAgICAgICAgICAgICAgICAnYmxtJyAgPSBjKCdmbG95ZCcgLCAnYmxhY2snLCAncG9saWNlJywgInByb3Rlc3QqIiwgJ2JsbScsJ2JsYWNrLWxpdmVzLW1hdHRlcicpLA0KICAgICAgICAgICAgICAgICdpbW1pZ3JhdGlvbicgPSBjKCdpbW1pZ3JhdConLCAgJ2JvcmRlcicsICdob21lbGFuZCcsICdtaWdyYW50KicpLA0KICAgICAgICAgICAgICAgICdjbGltYXRlJyA9IGMoJ2dsb2JhbCcsJ2NsaW1hdGUqJywnZnVlbCcsICdjYXJib24nLCAnZW1pc3Npb24qJyksDQogICAgICAgICAgICAgICAgJ2Fib3J0aW9uJyA9IGMoJ2Fib3J0KicsICAncm9lJywgJ3dhZGUnLCAnZmV0dXMnLCAnZG9iYnMnKSwNCiAgICAgICAgICAgICAgICAnZWNvbm9teScgPSBjKCdpbmZsYXRpb24nLCdwcmljZSonLCAndGF4JykNCiAgICAgICAgICAgICAgICApDQoNCiMgY29udmVydGVkIHRvIGEgcXVhbnRlZGEgZGljdGlvbmFyeQ0KZGljdF90b3BpYzwtZGljdGlvbmFyeShuZXdzX2RpY3QpDQojIGNvbnZlcnQgdGhlIHBvbF9kZm0gdG8gYSBzdHJ1Y3R1cmUgcHJlZmVycmVkIGJ5IHRoaXMgcGFja2FnZQ0Ka2V5QVRNX2RvY3M8LWtleUFUTV9yZWFkKHBvbF9kZm0pDQojIGNvbnZlcnQgdGhlIHF1YW50ZWRhIGRpY3Rpb25hcnkgdG8gYSBmb3JtYXQgdXNlZCBieSBrZXlBVE0gDQprZXlzPC1yZWFkX2tleXdvcmRzKGRvY3MgPSBrZXlBVE1fZG9jcywgZGljdGlvbmFyeSA9IGRpY3RfdG9waWMpDQoNCiN2aXN1YWxpemUga2V5d29yZHM6IA0Ka2V5X3ZpejwtdmlzdWFsaXplX2tleXdvcmRzKGRvY3MgPSBrZXlBVE1fZG9jcywga2V5d29yZHMgPSBrZXlzKQ0Ka2V5X3Zpeg0KYGBgDQoNCg0KQW5kIGhlcmUncyB0aGUgbW9kZWwgdHJhaW5pbmcgZnVuY3Rpb24uIFlvdSdsbCBwcm9iYWJseSBub3RpY2UgdGhhdCB0aGlzIGlzIHF1aXRlIGEgYml0IHNsb3dlciB0aGFuIGVpdGhlciB0aGUgc3RhbmRhcmQgb3Igc2VlZGVkIExEQS4gWW91IG1pZ2h0IHdhbnQgdG8gZ2V0IHRoaXMgcnVubmluZyBhbmQgZ28gZ3JhYiBhIHNuYWNrLiBUaGUgYXV0aG9ycyByZWNvbW1lbmQgc2F2aW5nIHRoZSBtb2RlbCBhZnRlciBpdCBpcyBmaW5pc2hlZCBydW5uaW5nLCBzbyBJJ20gZ29pbmcgdG8gZG8gdGhhdCBoZXJlLiBUbyBsb2FkIGl0IGJhY2sgdXAsIHlvdSB3b3VsZCBqdXN0IG5lZWQgdG8gcnVuIGBga2xkYV9tb2RlbCAgPC0gcmVhZFJEUygna2xkYS5yZHMnKSdgLiANCg0KYGBge3J9DQprbGRhX21vZGVsIDwtIGtleUFUTSgNCiAgZG9jcyAgICAgICAgICAgICAgPSBrZXlBVE1fZG9jcywgICAgIyB0ZXh0IGlucHV0DQogIG5vX2tleXdvcmRfdG9waWNzID0gNSwgICAgICAgICAgICAgICMgNSBleHRyYSBnYXJiYWdlIHRvcGljcw0KICBrZXl3b3JkcyAgICAgICAgICA9IGtleXMsICAgICAgICMgb3VyIGxpc3Qgb2YgdGVybXMNCiAgbW9kZWwgICAgICAgICAgICAgPSAiYmFzZSIsICAgICAgICAgIyB0aGUgYmFzZSBtb2RlbA0KICANCiAgb3B0aW9ucyAgICAgICAgICAgPSBsaXN0KHNlZWQgPSAyNTApICMgc2V0IHRoZSByYW5kb20gbnVtYmVyIHNlZWQgaW5zaWRlIHRoZSBmdW5jdGlvbg0KKQ0KDQojIHNhdmUgdGhlIHRyYWluZWQgbW9kZWwNCnNhdmVSRFMoa2xkYV9tb2RlbCwgZmlsZT0na2xkYS5yZHMnKQ0KYGBgDQoNCllvdSBjYW4gZ2V0IHRoZSBoaWdoZXN0IHByb2JhYmlsaXR5IHdvcmRzIHVzaW5nIHRoZSBgYHRvcF93b3Jkc2BgIGZ1bmN0aW9uLiBUaGUgcmVzdWx0aW5nIGRhdGFmcmFtZSB3aWxsIGhhdmUgYSBjaGVja21hcmsgbmV4dCB0byB0ZXJtcyB0aGF0IHdlcmUgcGFydCBvZiB5b3VyIGtleXdvcmQgbGlzdC4gDQoNCmBgYHtyfQ0KdG9wX3dvcmRzKGtsZGFfbW9kZWwpDQpgYGANCg0KDQpPbmNlIGFnYWluLCB3ZSBjYW4gdXNlIHRoaXMgdG8gcmV0cmlldmUgc3BlY2lmaWMgZG9jdW1lbnRzIChhbmQgdG8gY2hlY2sgdGhlIHF1YWxpdHkgb2Ygb3VyIHRvcGljcykNCg0KYGBge3J9DQplbGVjdGlvbl9kZjwtZG9jdmFycyhwb2xfZGZtKSU+JQ0KICAjIGFkZCAlIG9mIGNvdmlkIHRvcGljDQogIG11dGF0ZShlbGVjdGlvbj0ga2xkYV9tb2RlbCR0aGV0YVssJzNfZWxlY3Rpb25zJ10pJT4lDQogIGFycmFuZ2UoLWVsZWN0aW9uKQ0KDQplbGVjdGlvbl9kZiU+JQ0KICBzZWxlY3QodXJsKSU+JQ0KICBzbGljZV9oZWFkKG49MTApDQpgYGANCg0KDQpBbmQgd2UgY2FuIHVzZSB0aGlzIHRvIHRyYWNrIG5ld3MgY292ZXJhZ2Ugb3ZlciB0aW1lLiBIZXJlLCB3ZSBzZWUgcGVha3MgaW4gdGhlICJlbGVjdGlvbnMiIHRvcGljIHRoYXQgcm91Z2hseSBjb2luY2lkZSB3aXRoIHByZXNpZGVudGlhbCBnZW5lcmFsIGVsZWN0aW9uIHNlYXNvbnMgaW4gdGhlIHRpbWUgcGVyaW9kLiANCg0KYGBge3J9DQplbGVjdGlvbl9jb3ZlcmFnZTwtZWxlY3Rpb25fZGYlPiUNCiAgbXV0YXRlKCMgcm91bmQgZG93biBtb250aA0KICAgICAgICAgbW9udGggPSBmbG9vcl9kYXRlKGRhdGUsIHVuaXQ9J21vbnRocycpKSU+JQ0KICAjIGdyb3VwIGJ5IG1vbnRoIGFuZCBjb21wdXRlIHRoZSBtb250aGx5IGF2ZXJhZ2U6IA0KICBncm91cF9ieShtb250aCklPiUNCiAgc3VtbWFyaXNlKGVsZWN0aW9uPSBtZWFuKGVsZWN0aW9uKSkNCg0KZWxlY3Rpb25fY292ZXJhZ2UlPiUNCiAgZ2dwbG90KGFlcyh4PW1vbnRoLCB5PWVsZWN0aW9uKSkgKyANCiAgIyBjcmVhdGUgdGhlIGxpbmUgcGxvdA0KICBnZW9tX2xpbmUoKSArDQogIGdlb21fcG9pbnQoKSArDQogIHlsYWIoJ0F2ZXJhZ2UgdG9waWMgJScpICsNCiAgIyB1c2luZyB0aGUgbWluaW1hbCB0aGVtZTogDQogIHRoZW1lX21pbmltYWwoKSArDQogIGdndGl0bGUoJ0F2ZXJhZ2UgJSBlbGVjdGlvbiB0b3BpYyBieSBtb250aCcpIA0KDQpgYGANCg0KIyMjIEFkZGluZyBtZXRhZGF0YQ0KDQpUaGUgcmVhbCBmZWF0dXJlIHRoYXQgZGlzdGluZ3Vpc2hlcyBLZXl3b3JkIGFzc2lzdGVkIHRvcGljIG1vZGVscyBmcm9tIHNlZWRlZCBMREEgaXMgdGhlIGFiaWxpdHkgdG8gaW5jb3Jwb3JhdGUgZG9jdW1lbnQgbWV0YSBkYXRhIHRvIHByZWRpY3QgdG9waWMgcHJldmFsZW5jZSBhIHZlcnkgc2ltaWxhciBmYXNoaW9uIHRvIHRoZSBzdHJ1Y3R1cmFsIHRvcGljIG1vZGVsLiBUaGUgc3ludGF4IGlzIGhlcmUgc2xpZ2h0bHkgZGlmZmVyZW50LCBidXQgdGhlIGJhc2ljIGludHVpdGlvbiBoZXJlIGlzIHRoZSBzYW1lIGFzIGluIFNUTTogd2UgdGhpbmsgdGhhdCBGb3ggTmV3cyBhbmQgQ05OIHdpbGwgdmFyeSBzeXN0ZW1hdGljYWxseSBpbiB0aGUgYW1vdW50IG9mIGF0dGVudGlvbiB0aGV5IGdpdmUgdG8gZGlmZmVyZW50IHN1YmplY3RzLg0KKG5vdGU6IHRoaXMgd2lsbCB0YWtlIGEgd2hpbGUgdG8gY29udmVyZ2UpDQoNCmBgYHtyfQ0Ka2xkYV9jb3ZhcmlhdGUgPC0ga2V5QVRNKA0KICBkb2NzICAgICAgICAgICAgICA9IGtleUFUTV9kb2NzLA0KICBub19rZXl3b3JkX3RvcGljcyA9IDUsDQogIGtleXdvcmRzICAgICAgICAgID0ga2V5cywNCiAgbW9kZWwgICAgICAgICAgICAgPSAiY292YXJpYXRlcyIsDQogIG1vZGVsX3NldHRpbmdzICAgID0gbGlzdChjb3ZhcmlhdGVzX2RhdGEgICAgPSBkb2N2YXJzKHBvbF9kZm0pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgY292YXJpYXRlc19mb3JtdWxhID0gfiBzb3VyY2UpLA0KICBvcHRpb25zICAgICAgICAgICA9IGxpc3Qoc2VlZCA9IDI1MCkNCikNCg0KIyBzYXZlIHRoZSByZXN1bHRzIGFmdGVyIHJ1bm5pbmcgDQpzYXZlUkRTKGtsZGFfY292YXJpYXRlLCBmaWxlPSdrbGRhX2NvdmFyaWF0ZS5yZHMnKQ0KYGBgDQoNCldlIGNhbiB0aGUgdG9wIHdvcmRzIHVzaW5nIHRoZSBzYW1lIG1ldGhvZHMgYXMgYmVmb3JlLiBJdCdzIHBvc3NpYmxlIHRoYXQgdGhlIHJlc3VsdHMgaGVyZSB3aWxsIGhhdmUgaW1wcm92ZSBzb21ld2hhdCBiZWNhdXNlIHRoZSB0b3BpYyBtb2RlbCBpcyBub3cgaW5jb3Jwb3JhdGluZyBkYXRhIG9uIHRoZSBzb3VyY2UgaW50byB0aGUgbWV0YWRhdGEuIEhvd2V2ZXIsIHRoZSBtYWluIGZpbmRpbmcgb2YgaW50ZXJlc3Qgd2lsbCBsaWtlbHkgY29tZSBmcm9tIHZpZXdpbmcgdGhlIGltcGFjdCBvZiB0aGUgY292YXJpYXRlcyBvbiB0aGUgdG9waWMgcHJvYmFiaWxpdGllcy4gV2UgY2FuIHJldHJpZXZlIHRoZXNlIHdpdGggYGJ5X3N0cmF0YV9Eb2NUb3BpYygpYCBhbmQgcGxvdCB0aGVtIHVzaW5nIHRoZSBjb21tYW5kIGJlbG93LiBIZXJlLCB3ZSdyZSBwbG90dGluZyB0aGUgcHJldmFsZW5jZSBmb3IgdGhlIGltbWlncmF0aW9uIHRvcGljLiBUaGUgcmVzdWx0cyBzdWdnZXN0IHRoYXQgRm94IE5ld3MgaXMgc2lnbmlmaWNhbnRseSBtb3JlIGxpa2VseSB0byBjb3ZlciB0aGlzIGlzc3VlIChhdCBsZWFzdCBpbiB0aGlzIHNhbXBsZSkNCg0KYGBge3J9DQoNCiNzb21ldGltZXMgY292YXJpYXRlcyB3aWxsIGJlIHJlbmFtZWQgYXV0b21hdGljYWxseQ0KI3VzZSBjb3ZhcmlhdGVzX2luZm8oa2xkYV9jb3ZhcmlhdGUpIHRvIHNlZSBob3cgeW91ciBjb3ZhcmlhdGVzIG1pZ2h0IGhhdmUgYmVlbiByZW5hbWVkDQpzdHJhdGFfdG9waWMgPC0gYnlfc3RyYXRhX0RvY1RvcGljKA0KICBrbGRhX2NvdmFyaWF0ZSwgDQogICMgInNvdXJjZSIgaGVyZSB3YXMgYXV0b21hdGljYWxseSBjaGFuZ2VkIHRvIHNvdXJjZUZveCBOZXdzDQogIGJ5X3ZhciA9ICJzb3VyY2VGb3ggTmV3cyIsDQogIGxhYmVscyA9IGMoIkNOTiIsICJGb3giKQ0KKQ0KDQpwbG90KHN0cmF0YV90b3BpYywgdmFyX25hbWUgPSAnc291cmNlJywgc2hvd190b3BpYz0gNykNCg0KYGBgDQoNCiMjIEJFUlRvcGljDQoNCkxhc3RseSwgd2hpbGUgcHJvYmFibHkgb3V0c2lkZSB0aGUgc2NvcGUgb2YgdGhpcyBjb3Vyc2UsIEkgZG8gd2FudCB0byBoaWdobGlnaHQgYSBmaW5hbCB2YXJpYXRpb24gb2YgdGhlIHRvcGljIG1vZGVsIGtub3duIGFzIEJFUlRvcGljIFtwYXBlcl0oaHR0cHM6Ly9hcnhpdi5vcmcvcGRmLzIyMDMuMDU3OTQucGRmKS4gUmF0aGVyIHRoYW4gaW5mZXJyaW5nIHRvcGljcyB1c2luZyBhIGRvY3VtZW50LXRlcm0tbWF0cml4LCBCRVJUb3BpYyB1c2VzIGFuIGVtYmVkZGluZyBtb2RlbCAtIGEga2luZCBvZiBudW1lcmljIHJlcHJlc2VudGF0aW9uIG9mIHRleHQgdGhhdCBjYXB0dXJlcyBzZW1hbnRpYyBzaW1pbGFyaXRpZXMgYmV0d2VlbiB3b3Jkcywgc2VudGVuY2VzIG9yIGRvY3VtZW50cyAoZ29vZCBwcmltZXIgb24gdGhpcyBjb25jZXB0IFtoZXJlXShodHRwczovL3d3dy5waW5lY29uZS5pby9sZWFybi9zZXJpZXMvbmxwL2RlbnNlLXZlY3Rvci1lbWJlZGRpbmdzLW5scC8pKS4gSW1wb3J0YW50bHksIHdlIGNhbiB1c2UgcHJlLWV4aXN0aW5nIGVtYmVkZGluZ3MgdGhhdCB3ZXJlIHRyYWluZWQgb24gYSBodWdlIGNvcnB1cyBhbmQgYXBwbHkgdGhlbSB0byBhIG5ldyBzZXQgb2YgZG9jdW1lbnRzLCB3aGljaCBnaXZlcyB1cyBhIG1ham9yIGhlYWQtc3RhcnQgd2hlbiB0cnlpbmcgdG8gdHJhaW4gYSBtb2RlbC4gDQoNCg0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCiMgbG9hZCByZXRpY3VsYXRlIA0KbGlicmFyeShyZXRpY3VsYXRlKQ0KIyBydW4gdXNlX3B5dGhvbihwYXRoLXRvLXB5dGhvbiAuZXhlIGZpbGUpIGlmIHlvdSBuZWVkIHRvIHVzZSBhIHNwZWNpZmljIHZlcnNpb24NCg0KYGBgDQoNCg0KQkVSVG9waWMgZ2VuZXJhbGx5IHdvcmtzIGJldHRlciBvbiBwYXJhZ3JhcGhzIG9yIGV2ZW4gc2luZ2xlIHNlbnRlbmNlcywgc28gd2UgYXJlIGdvaW5nIHRvIHVzZSB0aGUgc2FtZSBkYXRhc2V0IGhlcmUsIGJ1dCBvbmx5IHRha2UgdGhlIGZpcnN0IDUgc2VudGVuY2VzIGZyb20gZWFjaCBhcnRpY2xlLiBPdGhlciB0aGFuIHRoYXQsIHdlIHJlYWxseSBkb24ndCBuZWVkIHRvIGRvICphbnkqIHByZS1jbGVhbmluZyBvZiB0aGUgZGF0YSBiZWNhdXNlIFt0aGUgc2VudGVuY2UgZW1iZWRkaW5nIG1vZGVsXShodHRwczovL2FyeGl2Lm9yZy9hYnMvMTkwOC4xMDA4NCkgd2UncmUgYXBwbHlpbmcgaGVyZSB3YXMgdHJhaW5lZCBvbiByZWd1bGFyIGh1bWFuIHNlbnRlbmNlcyBhbmQgY2FuIHVzZSB3b3JkIG9yZGVyIGFuZCBjb250ZXh0IGNsdWVzIHdoZW4gcHJvZHVjaW5nIG51bWVyaWMgcmVwcmVzZW50YXRpb25zIG9mIHNlbnRlbmNlcy4gDQoNCmBgYHtweXRob24sIGV2YWw9RkFMU0V9DQoNCmltcG9ydCBwYW5kYXMgYXMgcGQNCmZyb20gbmx0ay50b2tlbml6ZSBpbXBvcnQgc2VudF90b2tlbml6ZSwgd29yZF90b2tlbml6ZQ0KZnJvbSBiZXJ0b3BpYyBpbXBvcnQgQkVSVG9waWMNCmZyb20gc2VudGVuY2VfdHJhbnNmb3JtZXJzIGltcG9ydCBTZW50ZW5jZVRyYW5zZm9ybWVyDQoNCiMgcmVhZCB0aGUgY29ycHVzDQpwb2xkb2NzID0gcGQucmVhZF9jc3YoJ2h0dHBzOi8vZ2l0aHViLmNvbS9OZWlsYmx1bmQvQVBBTi9yYXcvbWFpbi9uZXdzX3NhbXBsZS5jc3YnKQ0KDQojIHNwbGl0IHRoZSB0ZXh0IGludG8gc2VudGVuY2VzIA0Kc2VudGVuY2VzID0gW3NlbnRfdG9rZW5pemUodGV4dCkgZm9yIHRleHQgaW4gcG9sZG9jcy50ZXh0XQ0KIyBnZXQgdGhlIGZpcnN0IHRocmVlIHNlbnRlbmNlcyBmcm9tIGVhY2ggdGV4dA0Kc2VudGVuY2VzID0gWycgJy5qb2luKHhbOjRdKSBmb3IgeCBpbiBzZW50ZW5jZXNdDQoNCg0KIyBhcHBseSBhIHByZS10cmFpbmVkIGVtYmVkZGluZyBtb2RlbCBvbiB0aGUgc2VudGVuY2VzDQplbWJlZGRpbmdfbW9kZWwgPSBTZW50ZW5jZVRyYW5zZm9ybWVyKCJhbGwtTWluaUxNLUw2LXYyIikNCmVtYmVkZGluZ3MgPSBlbWJlZGRpbmdfbW9kZWwuZW5jb2RlKHNlbnRlbmNlcywgc2hvd19wcm9ncmVzc19iYXI9RmFsc2UpDQoNCg0KdG9waWNfbW9kZWwgPSBCRVJUb3BpYyhlbWJlZGRpbmdfbW9kZWwgPSBlbWJlZGRpbmdfbW9kZWwpDQoNCiMgdHJhaW4gdGhlIG1vZGVsDQp0b3BpY3MsIHByb2JzID0gdG9waWNfbW9kZWwuZml0X3RyYW5zZm9ybShzZW50ZW5jZXMsIGVtYmVkZGluZ3MpDQogDQoNCiMgc2F2ZSB0aGUgcmVzdWx0cyBpbiB0aGUgd29ya2luZyBkaXJlY3RvcnkNCnRvcGljX21vZGVsLnNhdmUoIi4vYmVydG1vZGVsIiwgc2VyaWFsaXphdGlvbj0ic2FmZXRlbnNvcnMiLCBzYXZlX2N0ZmlkZj1UcnVlLCBzYXZlX2VtYmVkZGluZ19tb2RlbD1lbWJlZGRpbmdfbW9kZWwpDQoNCmBgYA0KDQoNClRoZSBiZXJ0b3BpYyBwYWNrYWdlIG9mZmVycyBhIG51bWJlciBvZiBvcHRpb25zIGZvciB2aXN1YWxpemluZyBvdXIgcmVzdWx0cy4gVGhlIGZpcnN0IHBsb3Qgc2hvd3MgYW4gaW50ZXItdG9waWMgZGlzdGFuY2UgbWFwIChzaW1pbGFyIHRvIHRoZSBraW5kIHByb3ZpZGVkIGJ5IExEQVZpcykuIE5vdGFibHk6IHRoZSBCRVJUb3BpYyBtb2RlbCBpbmZlcnJlZCBhIG11Y2ggbGFyZ2VyIG51bWJlciBvZiB0b3BpY3MgKH42MCkgaGVyZSB0aGFuIHdoYXQgd2Ugc2V0IGluIHByZXZpb3VzIGl0ZXJhdGlvbnMsIGFuZCBtYW55IGFwcGVhciB0byByZWZsZWN0IGEgbXVjaCBmaW5lciBsZXZlbCBvZiBkZXRhaWwuIA0KDQoNCmBgYHtweXRob259DQpmcm9tIGJlcnRvcGljIGltcG9ydCBCRVJUb3BpYw0KDQojIGxvYWRpbmcgdGhlIHNhdmVkIG1vZGVsDQpsb2FkZWRfbW9kZWwgPSBCRVJUb3BpYy5sb2FkKCIuL2JlcnRtb2RlbCIpDQoNCiMgdmlzdWFsaXplIHRvcGljIHNpbWlsYXJpdGllczoNCmxvYWRlZF9tb2RlbC52aXN1YWxpemVfdG9waWNzKCkNCg0KbG9hZGVkX21vZGVsLnZpc3VhbGl6ZV9iYXJjaGFydCh0b3BpY3MgPSBbIDEsMywgNCwgMTQsIDE3XSApDQojIGdldHRpbmcgdG9waWMgY291bnRzIGJ5IHNvdXJjZQ0KY2xhc3NlcyA9IHIucG9sZGYuc291cmNlDQp0b3BpY3NfcGVyX2NsYXNzID0gbG9hZGVkX21vZGVsLnRvcGljc19wZXJfY2xhc3Moci5wb2xkZi50ZXh0LCBjbGFzc2VzPWNsYXNzZXMpDQoNCiMgYWJvcnRpb24sIHVrcmFpbmUsIGh1bnRlciBiaWRlbiwgZ2VuZGVyLCBhbmQgamFuIDYNCmxvYWRlZF9tb2RlbC52aXN1YWxpemVfdG9waWNzX3Blcl9jbGFzcyh0b3BpY3NfcGVyX2NsYXNzLCB0b3BpY3MgPSBbIDEsMywgNCwgMTQsIDE3XSwgbm9ybWFsaXplX2ZyZXF1ZW5jeT0gVHJ1ZSkNCg0KDQoNCmBgYA0KDQoNCg0KIyBPdGhlciBzdHVmZiB0byBsb29rIGF0DQoNCk1hbnkgb2YgdGhlc2UgZXhhbXBsZXMgd2VyZSBhZGFwdGVkIGZyb20gdGhlIFF1YW50ZWRhIGRvY3VtZW50YXRpb24uIFRoZSBwYWNrYWdlIGRvY3VtZW50YXRpb24gaXMgW2hlcmVdKGh0dHA6Ly9xdWFudGVkYS5pby8pLCBhbmQgdGhlcmUgaXMgKGFsc28gYSB0dXRvcmlhbCBbaGVyZV0oaHR0cHM6Ly90dXRvcmlhbHMucXVhbnRlZGEuaW8vaW50cm9kdWN0aW9uLykuIE9uZSBpbXBvcnRhbnQgYXJlYSBvZiB0ZXh0IGFuYWx5c2lzIG5vdCBjb3ZlcmVkIGhlcmUgYXJlIGZ1bGx5IHN1cGVydmlzZWQgbW9kZWxzLCB3aGVyZSB5b3UgdGFrZSBhIHNldCBvZiBsYWJlbGVkIGRvY3VtZW50cyBhbmQgdHJhaW4gYSBtb2RlbCB0byBwcmVkaWN0IGEgc2V0IG9mIHVubGFiZWxlZCBkb2N1bWVudHMuIFdoaWxlIHVzZWZ1bCBhbmQgYW4gaW1wb3J0YW50IHBhcnQgb2YgdGhlIHRleHQgYW5hbHlzaXMgZmllbGQgbW9yZSBicm9hZGx5LCBpdCB0aGVzZSBtb2RlbHMgdHlwaWNhbGx5IHJlcXVpcmUgYSBsb3Qgb2YgaGFuZCBjb2RpbmcgYmVmb3JlIHRoZXkgcmVhbGx5IGJlZ2luIHRvIGJlIGVmZmVjdGl2ZS4gVGhhdCBzYWlkLCB0aGUgcXVhbnRlZGEgdHV0b3JpYWwgYWJvdmUgZG9lcyBnbyBpbnRvIHNvbWUgZGV0YWlsIG9uIGhvdyB0byB0cmFpbiBhIGJhc2ljIHRleHQgY2xhc3NpZmllciBpbiBSLiANCg0KSWYgeW91J3JlIHJlYWxseSBpbnRlcmVzdGVkIGluIGdvaW5nIGluIGRlcHRoLCBvbmUgb2YgdGhlIGJlc3Qgc291cmNlcyBvbiB0aGlzIGlzIFtEYW4gSnVyYWZza3ldKGh0dHA6Ly93ZWIuc3RhbmZvcmQuZWR1L35qdXJhZnNreS8pIHdobyBtYWtlcyBzbGlkZXMsIGxlY3R1cmVzLCBhbmQgW2V2ZW4gYW4gZW50aXJlIHRleHRib29rXShodHRwczovL3dlYi5zdGFuZm9yZC5lZHUvfmp1cmFmc2t5L3NscDMvKSBmcmVlbHkgYXZhaWxhYmxlIG9ubGluZS4gDQoNCg==